diff --git a/aws/resource_aws_ecs_service.go b/aws/resource_aws_ecs_service.go index 6f51bf0a565..a63e4843ccd 100644 --- a/aws/resource_aws_ecs_service.go +++ b/aws/resource_aws_ecs_service.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "regexp" + "strconv" "strings" "time" @@ -206,6 +207,12 @@ func resourceAwsEcsService() *schema.Resource { }, }, }, + + "wait_for_steady_state": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, }, } } @@ -336,6 +343,12 @@ func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[DEBUG] ECS service created: %s", *service.ServiceArn) d.SetId(*service.ServiceArn) + if d.Get("wait_for_steady_state").(bool) { + if err = resourceAwsEcsWaitForServiceSteadyState(d, meta); err != nil { + return err + } + } + return resourceAwsEcsServiceRead(d, meta) } @@ -570,6 +583,12 @@ func resourceAwsEcsServiceUpdate(d *schema.ResourceData, meta interface{}) error return err } + if d.Get("wait_for_steady_state").(bool) { + if err = resourceAwsEcsWaitForServiceSteadyState(d, meta); err != nil { + return err + } + } + return resourceAwsEcsServiceRead(d, meta) } @@ -711,3 +730,66 @@ func validateAwsEcsServiceHealthCheckGracePeriodSeconds(v interface{}, k string) } return } + +func resourceAwsEcsWaitForServiceSteadyState(d *schema.ResourceData, meta interface{}) error { + log.Println("[INFO] Waiting for service to reach a steady state") + + steadyStateConf := &resource.StateChangeConf{ + Pending: []string{"false"}, + Target: []string{"true"}, + Refresh: resourceAwsEcsServiceIsSteadyStateFunc(d, meta), + Timeout: 10 * time.Minute, + MinTimeout: 1 * time.Second, + } + + _, err := steadyStateConf.WaitForState() + return err +} + +// Returns a StateRefreshFunc for a given service. That function will return "true" if the service is in a +// steady state, "false" if the service is running but not in a steady state, and an error otherwise. +func resourceAwsEcsServiceIsSteadyStateFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + conn := meta.(*AWSClient).ecsconn + in := &ecs.DescribeServicesInput{ + Services: []*string{aws.String(d.Id())}, + Cluster: aws.String(d.Get("cluster").(string)), + } + + out, err := conn.DescribeServices(in) + if err != nil { + return nil, "", err + } + + if len(out.Services) < 1 { + if d.IsNewResource() { + // Continue retrying. It's *possible* the newly-created service was deleted out from under us, + // but more likely we saw a stale read from ECS. + log.Printf("[INFO] New ECS service not found yet: %q", d.Id()) + return nil, "false", nil + } + + return nil, "", fmt.Errorf( + "Service %v disappeared while waiting for it to reach a steady state", + d.Id()) + } + + service := out.Services[0] + + // A service is in a steady state if: + // 1. It is not INACTIVE or DRAINING + // 2. There is only one deployment, which will be PRIMARY + // 3. The count of running tasks matches the desired count + // ref: https://github.com/boto/botocore/blob/3ac0dd53/botocore/data/ecs/2014-11-13/waiters-2.json#L42-L72 + if *service.Status == "INACTIVE" || *service.Status == "DRAINING" { + return nil, "", fmt.Errorf( + "Service %v can't reach steady state because its status is %v", + d.Id(), *service.Status) + } + + isSteadyState := len(service.Deployments) == 1 && + *service.RunningCount == *service.DesiredCount + + return service, strconv.FormatBool(isSteadyState), nil + } +} diff --git a/website/docs/r/ecs_service.html.markdown b/website/docs/r/ecs_service.html.markdown index 395ec38c20d..766fd16775a 100644 --- a/website/docs/r/ecs_service.html.markdown +++ b/website/docs/r/ecs_service.html.markdown @@ -63,6 +63,7 @@ into consideration during task placement. The maximum number of * `placement_constraints` - (Optional) rules that are taken into consideration during task placement. Maximum number of `placement_constraints` is `10`. Defined below. * `network_configuration` - (Optional) The network configuration for the service. This parameter is required for task definitions that use the awsvpc network mode to receive their own Elastic Network Interface, and it is not supported for other network modes. +* `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`. -> **Note:** As a result of an AWS limitation, a single `load_balancer` can be attached to the ECS service at most. See [related docs](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-load-balancing.html#load-balancing-concepts).