Skip to content

Commit

Permalink
Add wait_for_steady_state attribute to ECS service
Browse files Browse the repository at this point in the history
If true, creates and updates will only complete once the service has
reached a steady state.
  • Loading branch information
mmdriley committed Mar 28, 2018
1 parent 7a71dac commit fb6a5eb
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 0 deletions.
82 changes: 82 additions & 0 deletions aws/resource_aws_ecs_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"regexp"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -206,6 +207,12 @@ func resourceAwsEcsService() *schema.Resource {
},
},
},

"wait_for_steady_state": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
}
}
1 change: 1 addition & 0 deletions website/docs/r/ecs_service.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down

0 comments on commit fb6a5eb

Please sign in to comment.