diff --git a/aws/resource_aws_ecs_service.go b/aws/resource_aws_ecs_service.go index 429b146514d..c141056c8f8 100644 --- a/aws/resource_aws_ecs_service.go +++ b/aws/resource_aws_ecs_service.go @@ -353,6 +353,12 @@ func resourceAwsEcsService() *schema.Resource { }, }, "tags": tagsSchema(), + + "wait_for_steady_state": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, }, } } @@ -528,6 +534,12 @@ func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[DEBUG] ECS service created: %s", aws.StringValue(service.ServiceArn)) d.SetId(aws.StringValue(service.ServiceArn)) + if d.Get("wait_for_steady_state").(bool) { + if err := waitForSteadyState(conn, d); err != nil { + return err + } + } + return resourceAwsEcsServiceRead(d, meta) } @@ -1035,6 +1047,12 @@ func resourceAwsEcsServiceUpdate(d *schema.ResourceData, meta interface{}) error } } + if d.Get("wait_for_steady_state").(bool) { + if err := waitForSteadyState(conn, d); err != nil { + return err + } + } + if d.HasChange("tags") { o, n := d.GetChange("tags") @@ -1164,3 +1182,17 @@ func buildFamilyAndRevisionFromARN(arn string) string { func getNameFromARN(arn string) string { return strings.Split(arn, "/")[1] } + +func waitForSteadyState(conn *ecs.ECS, d *schema.ResourceData) error { + input := &ecs.DescribeServicesInput{ + Services: aws.StringSlice([]string{d.Id()}), + } + if v, ok := d.GetOk("cluster"); ok { + input.Cluster = aws.String(v.(string)) + } + + if err := conn.WaitUntilServicesStable(input); err != nil { + return fmt.Errorf("error waiting for service (%s) to reach a steady state: %w", d.Id(), err) + } + return nil +} diff --git a/aws/resource_aws_ecs_service_test.go b/aws/resource_aws_ecs_service_test.go index e9153f28a95..badbcb484b5 100644 --- a/aws/resource_aws_ecs_service_test.go +++ b/aws/resource_aws_ecs_service_test.go @@ -166,6 +166,8 @@ func TestAccAWSEcsService_basicImport(t *testing.T) { ImportStateId: importInput, ImportState: true, ImportStateVerify: true, + // wait_for_steady_state is not read from API + ImportStateVerifyIgnore: []string{"wait_for_steady_state"}, }, // Test non-existent resource import { @@ -452,7 +454,8 @@ func TestAccAWSEcsService_withDeploymentController_Type_CodeDeploy(t *testing.T) ImportState: true, ImportStateVerify: true, // Resource currently defaults to importing task_definition as family:revision - ImportStateVerifyIgnore: []string{"task_definition"}, + // and wait_for_steady_state is not read from API + ImportStateVerifyIgnore: []string{"task_definition", "wait_for_steady_state"}, }, }, }) @@ -481,6 +484,8 @@ func TestAccAWSEcsService_withDeploymentController_Type_External(t *testing.T) { ImportStateId: fmt.Sprintf("%s/%s", rName, rName), ImportState: true, ImportStateVerify: true, + // wait_for_steady_state is not read from API + ImportStateVerifyIgnore: []string{"wait_for_steady_state"}, }, }, }) @@ -894,6 +899,81 @@ func TestAccAWSEcsService_withLaunchTypeFargateAndPlatformVersion(t *testing.T) }) } +func TestAccAWSEcsService_withLaunchTypeFargateAndWaitForSteadyState(t *testing.T) { + var service ecs.Service + resourceName := "aws_ecs_service.test" + rString := acctest.RandString(8) + rName := fmt.Sprintf("tf-acc-svc-w-ltf-ss-%s", rString) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcsServiceDestroy, + Steps: []resource.TestStep{ + { + // Wait for the ECS Cluster to reach a steady state w/specified count + Config: testAccAWSEcsServiceWithLaunchTypeFargateAndWait(rName, 1, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsServiceExists(resourceName, &service), + resource.TestCheckResourceAttr(resourceName, "desired_count", "1"), + resource.TestCheckResourceAttr(resourceName, "wait_for_steady_state", "true"), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", rName, rName), + ImportState: true, + ImportStateVerify: true, + // Resource currently defaults to importing task_definition as family:revision + // and wait_for_steady_state is not read from API + ImportStateVerifyIgnore: []string{"task_definition", "wait_for_steady_state"}, + }, + }, + }) +} + +func TestAccAWSEcsService_withLaunchTypeFargateAndUpdateWaitForSteadyState(t *testing.T) { + var service ecs.Service + resourceName := "aws_ecs_service.test" + rString := acctest.RandString(8) + + rName := fmt.Sprintf("tf-acc-svc-w-ltf-ss-%s", rString) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcsServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEcsServiceWithLaunchTypeFargateWithoutWait(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsServiceExists(resourceName, &service), + resource.TestCheckResourceAttr(resourceName, "desired_count", "1"), + resource.TestCheckResourceAttr(resourceName, "wait_for_steady_state", "false"), + ), + }, + { + // Modify desired count and wait for the ECS Cluster to reach steady state + Config: testAccAWSEcsServiceWithLaunchTypeFargateAndWait(rName, 2, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsServiceExists(resourceName, &service), + resource.TestCheckResourceAttr(resourceName, "desired_count", "2"), + resource.TestCheckResourceAttr(resourceName, "wait_for_steady_state", "true"), + ), + }, + { + // Modify desired count without wait + Config: testAccAWSEcsServiceWithLaunchTypeFargateAndWait(rName, 1, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsServiceExists(resourceName, &service), + resource.TestCheckResourceAttr(resourceName, "desired_count", "1"), + resource.TestCheckResourceAttr(resourceName, "wait_for_steady_state", "false"), + ), + }, + }, + }) +} + func TestAccAWSEcsService_withLaunchTypeEC2AndNetworkConfiguration(t *testing.T) { var service ecs.Service rString := acctest.RandString(8) @@ -1075,7 +1155,8 @@ func TestAccAWSEcsService_Tags(t *testing.T) { ImportState: true, ImportStateVerify: true, // Resource currently defaults to importing task_definition as family:revision - ImportStateVerifyIgnore: []string{"task_definition"}, + // and wait_for_steady_state is not read from API + ImportStateVerifyIgnore: []string{"task_definition", "wait_for_steady_state"}, }, { Config: testAccAWSEcsServiceConfigTags2(rName, "key1", "value1updated", "key2", "value2"), @@ -1350,6 +1431,235 @@ resource "aws_ecs_service" "mongo" { `, clusterName, tdName, svcName) } +func testAccAWSEcsServiceWithLaunchTypeFargateWithoutWait(rName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.10.0.0/16" + + tags = { + Name = "terraform-testacc-ecs-service-with-launch-type-fargate" + } +} + +resource "aws_subnet" "test" { + count = 2 + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) + availability_zone = data.aws_availability_zones.available.names[count.index] + vpc_id = aws_vpc.test.id + + tags = { + Name = "tf-acc-ecs-service-with-launch-type-fargate" + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.test.id + } +} + +resource "aws_route_table_association" "test" { + count = 2 + subnet_id = element(aws_subnet.test.*.id, count.index) + route_table_id = aws_route_table.test.id +} + +resource "aws_security_group" "test" { + name = %[1]q + description = "Allow traffic" + vpc_id = aws_vpc.test.id + + ingress { + protocol = "6" + from_port = 80 + to_port = 8000 + cidr_blocks = [aws_vpc.test.cidr_block] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + + cidr_blocks = [ + "0.0.0.0/0", + ] + } +} + +resource "aws_ecs_cluster" "test" { + name = %[1]q +} + +resource "aws_ecs_task_definition" "mongo" { + family = %[1]q + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = "256" + memory = "512" + + container_definitions = <