2014-07-10 01:00:11 +02:00
package aws
import (
"fmt"
"log"
2014-12-12 23:21:20 +01:00
"strings"
2014-10-18 05:10:52 +02:00
"time"
2014-07-10 01:00:11 +02:00
2014-10-18 05:10:52 +02:00
"github.com/hashicorp/terraform/helper/resource"
2014-10-10 23:34:40 +02:00
"github.com/hashicorp/terraform/helper/schema"
2015-02-20 15:55:54 +01:00
2015-06-03 20:36:57 +02:00
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/elb"
2014-07-10 01:00:11 +02:00
)
2014-10-10 23:34:40 +02:00
func resourceAwsAutoscalingGroup ( ) * schema . Resource {
return & schema . Resource {
Create : resourceAwsAutoscalingGroupCreate ,
Read : resourceAwsAutoscalingGroupRead ,
Update : resourceAwsAutoscalingGroupUpdate ,
Delete : resourceAwsAutoscalingGroupDelete ,
Schema : map [ string ] * schema . Schema {
"name" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
2015-06-25 16:01:40 +02:00
ValidateFunc : func ( v interface { } , k string ) ( ws [ ] string , errors [ ] error ) {
// https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1862-L1873
value := v . ( string )
if len ( value ) > 255 {
errors = append ( errors , fmt . Errorf (
"%q cannot be longer than 255 characters" , k ) )
}
return
} ,
2014-10-10 23:34:40 +02:00
} ,
"launch_configuration" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
} ,
"desired_capacity" : & schema . Schema {
Type : schema . TypeInt ,
Optional : true ,
Computed : true ,
} ,
2015-05-14 19:45:21 +02:00
"min_elb_capacity" : & schema . Schema {
Type : schema . TypeInt ,
Optional : true ,
} ,
2014-10-10 23:34:40 +02:00
"min_size" : & schema . Schema {
Type : schema . TypeInt ,
Required : true ,
} ,
"max_size" : & schema . Schema {
Type : schema . TypeInt ,
Required : true ,
} ,
"default_cooldown" : & schema . Schema {
Type : schema . TypeInt ,
Optional : true ,
Computed : true ,
} ,
"force_delete" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
2015-10-12 22:50:07 +02:00
Default : false ,
2014-10-10 23:34:40 +02:00
} ,
"health_check_grace_period" : & schema . Schema {
Type : schema . TypeInt ,
Optional : true ,
Computed : true ,
} ,
"health_check_type" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
} ,
"availability_zones" : & schema . Schema {
Type : schema . TypeSet ,
2015-07-14 17:19:10 +02:00
Optional : true ,
2014-10-10 23:34:40 +02:00
Elem : & schema . Schema { Type : schema . TypeString } ,
2015-04-09 15:38:16 +02:00
Set : schema . HashString ,
2014-10-10 23:34:40 +02:00
} ,
2015-10-30 22:45:19 +01:00
"placement_group" : & schema . Schema {
2015-11-02 16:26:25 +01:00
Type : schema . TypeString ,
2015-10-30 22:45:19 +01:00
Optional : true ,
} ,
2014-10-10 23:34:40 +02:00
"load_balancers" : & schema . Schema {
Type : schema . TypeSet ,
Optional : true ,
Elem : & schema . Schema { Type : schema . TypeString } ,
2015-04-09 15:38:16 +02:00
Set : schema . HashString ,
2014-10-10 23:34:40 +02:00
} ,
"vpc_zone_identifier" : & schema . Schema {
Type : schema . TypeSet ,
Optional : true ,
Computed : true ,
Elem : & schema . Schema { Type : schema . TypeString } ,
2015-04-09 15:38:16 +02:00
Set : schema . HashString ,
2014-10-10 23:34:40 +02:00
} ,
2014-10-23 23:58:54 +02:00
"termination_policies" : & schema . Schema {
2015-07-29 23:44:02 +02:00
Type : schema . TypeList ,
2014-10-23 23:58:54 +02:00
Optional : true ,
Elem : & schema . Schema { Type : schema . TypeString } ,
} ,
2015-03-03 23:36:25 +01:00
2015-09-08 20:15:30 +02:00
"wait_for_capacity_timeout" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
Default : "10m" ,
ValidateFunc : func ( v interface { } , k string ) ( ws [ ] string , errors [ ] error ) {
value := v . ( string )
duration , err := time . ParseDuration ( value )
if err != nil {
errors = append ( errors , fmt . Errorf (
"%q cannot be parsed as a duration: %s" , k , err ) )
}
if duration < 0 {
errors = append ( errors , fmt . Errorf (
"%q must be greater than zero" , k ) )
}
return
} ,
} ,
2015-03-03 23:36:25 +01:00
"tag" : autoscalingTagsSchema ( ) ,
2014-10-10 23:34:40 +02:00
} ,
2014-07-10 01:00:11 +02:00
}
2014-10-10 23:34:40 +02:00
}
2014-07-10 01:00:11 +02:00
2014-10-10 23:34:40 +02:00
func resourceAwsAutoscalingGroupCreate ( d * schema . ResourceData , meta interface { } ) error {
2015-05-07 01:54:59 +02:00
conn := meta . ( * AWSClient ) . autoscalingconn
2014-07-10 01:00:11 +02:00
2015-04-15 22:30:35 +02:00
var autoScalingGroupOpts autoscaling . CreateAutoScalingGroupInput
2015-02-20 15:55:54 +01:00
autoScalingGroupOpts . AutoScalingGroupName = aws . String ( d . Get ( "name" ) . ( string ) )
autoScalingGroupOpts . LaunchConfigurationName = aws . String ( d . Get ( "launch_configuration" ) . ( string ) )
2015-07-28 22:29:46 +02:00
autoScalingGroupOpts . MinSize = aws . Int64 ( int64 ( d . Get ( "min_size" ) . ( int ) ) )
autoScalingGroupOpts . MaxSize = aws . Int64 ( int64 ( d . Get ( "max_size" ) . ( int ) ) )
2015-07-14 17:19:10 +02:00
// Availability Zones are optional if VPC Zone Identifer(s) are specified
if v , ok := d . GetOk ( "availability_zones" ) ; ok && v . ( * schema . Set ) . Len ( ) > 0 {
autoScalingGroupOpts . AvailabilityZones = expandStringList ( v . ( * schema . Set ) . List ( ) )
}
2014-10-10 23:34:40 +02:00
2015-03-03 23:36:25 +01:00
if v , ok := d . GetOk ( "tag" ) ; ok {
autoScalingGroupOpts . Tags = autoscalingTagsFromMap (
setToMapByKey ( v . ( * schema . Set ) , "key" ) , d . Get ( "name" ) . ( string ) )
}
2014-10-10 23:34:40 +02:00
if v , ok := d . GetOk ( "default_cooldown" ) ; ok {
2015-07-28 22:29:46 +02:00
autoScalingGroupOpts . DefaultCooldown = aws . Int64 ( int64 ( v . ( int ) ) )
2014-07-10 01:00:11 +02:00
}
2015-03-20 16:11:12 +01:00
if v , ok := d . GetOk ( "health_check_type" ) ; ok && v . ( string ) != "" {
2015-02-25 22:46:56 +01:00
autoScalingGroupOpts . HealthCheckType = aws . String ( v . ( string ) )
}
2014-10-10 23:34:40 +02:00
if v , ok := d . GetOk ( "desired_capacity" ) ; ok {
2015-07-28 22:29:46 +02:00
autoScalingGroupOpts . DesiredCapacity = aws . Int64 ( int64 ( v . ( int ) ) )
2014-07-10 01:00:11 +02:00
}
2014-10-10 23:34:40 +02:00
if v , ok := d . GetOk ( "health_check_grace_period" ) ; ok {
2015-07-28 22:29:46 +02:00
autoScalingGroupOpts . HealthCheckGracePeriod = aws . Int64 ( int64 ( v . ( int ) ) )
2014-07-10 01:00:11 +02:00
}
2015-10-30 22:45:19 +01:00
if v , ok := d . GetOk ( "placement_group" ) ; ok && v . ( * schema . Set ) . Len ( ) > 0 {
autoScalingGroupOpts . PlacementGroup = expandStringList (
v . ( * schema . Set ) . List ( ) )
}
2014-07-10 01:00:11 +02:00
2015-02-18 01:12:02 +01:00
if v , ok := d . GetOk ( "load_balancers" ) ; ok && v . ( * schema . Set ) . Len ( ) > 0 {
2015-04-16 22:28:18 +02:00
autoScalingGroupOpts . LoadBalancerNames = expandStringList (
2014-10-10 23:34:40 +02:00
v . ( * schema . Set ) . List ( ) )
2014-07-10 01:00:11 +02:00
}
2015-02-18 01:12:02 +01:00
if v , ok := d . GetOk ( "vpc_zone_identifier" ) ; ok && v . ( * schema . Set ) . Len ( ) > 0 {
2015-07-14 17:19:10 +02:00
autoScalingGroupOpts . VPCZoneIdentifier = expandVpcZoneIdentifiers ( v . ( * schema . Set ) . List ( ) )
2014-07-10 01:00:11 +02:00
}
2014-12-10 02:11:50 +01:00
2015-07-29 23:44:02 +02:00
if v , ok := d . GetOk ( "termination_policies" ) ; ok && len ( v . ( [ ] interface { } ) ) > 0 {
autoScalingGroupOpts . TerminationPolicies = expandStringList ( v . ( [ ] interface { } ) )
2014-10-23 23:58:54 +02:00
}
2014-07-10 01:00:11 +02:00
2014-07-14 17:36:25 +02:00
log . Printf ( "[DEBUG] AutoScaling Group create configuration: %#v" , autoScalingGroupOpts )
2015-05-07 01:54:59 +02:00
_ , err := conn . CreateAutoScalingGroup ( & autoScalingGroupOpts )
2014-07-10 01:00:11 +02:00
if err != nil {
2014-10-10 23:34:40 +02:00
return fmt . Errorf ( "Error creating Autoscaling Group: %s" , err )
2014-07-10 01:00:11 +02:00
}
2014-10-10 23:34:40 +02:00
d . SetId ( d . Get ( "name" ) . ( string ) )
log . Printf ( "[INFO] AutoScaling Group ID: %s" , d . Id ( ) )
2014-07-10 01:00:11 +02:00
2015-05-07 01:34:20 +02:00
if err := waitForASGCapacity ( d , meta ) ; err != nil {
return err
}
2014-10-10 23:34:40 +02:00
return resourceAwsAutoscalingGroupRead ( d , meta )
2014-07-10 01:00:11 +02:00
}
2014-11-21 17:58:34 +01:00
func resourceAwsAutoscalingGroupRead ( d * schema . ResourceData , meta interface { } ) error {
g , err := getAwsAutoscalingGroup ( d , meta )
if err != nil {
return err
}
if g == nil {
return nil
}
d . Set ( "availability_zones" , g . AvailabilityZones )
2015-03-26 20:49:15 +01:00
d . Set ( "default_cooldown" , g . DefaultCooldown )
d . Set ( "desired_capacity" , g . DesiredCapacity )
d . Set ( "health_check_grace_period" , g . HealthCheckGracePeriod )
d . Set ( "health_check_type" , g . HealthCheckType )
d . Set ( "launch_configuration" , g . LaunchConfigurationName )
2014-11-21 17:58:34 +01:00
d . Set ( "load_balancers" , g . LoadBalancerNames )
2015-03-26 20:49:15 +01:00
d . Set ( "min_size" , g . MinSize )
d . Set ( "max_size" , g . MaxSize )
d . Set ( "name" , g . AutoScalingGroupName )
2015-03-03 23:36:25 +01:00
d . Set ( "tag" , g . Tags )
2015-02-20 15:55:54 +01:00
d . Set ( "vpc_zone_identifier" , strings . Split ( * g . VPCZoneIdentifier , "," ) )
2015-02-04 17:00:03 +01:00
d . Set ( "termination_policies" , g . TerminationPolicies )
2014-11-21 17:58:34 +01:00
return nil
}
2014-10-10 23:34:40 +02:00
func resourceAwsAutoscalingGroupUpdate ( d * schema . ResourceData , meta interface { } ) error {
2015-05-07 01:54:59 +02:00
conn := meta . ( * AWSClient ) . autoscalingconn
2014-07-10 01:00:11 +02:00
2015-04-15 22:30:35 +02:00
opts := autoscaling . UpdateAutoScalingGroupInput {
2015-02-20 15:55:54 +01:00
AutoScalingGroupName : aws . String ( d . Id ( ) ) ,
2014-07-15 23:20:54 +02:00
}
2015-06-26 07:51:31 +02:00
if d . HasChange ( "default_cooldown" ) {
2015-07-28 22:29:46 +02:00
opts . DefaultCooldown = aws . Int64 ( int64 ( d . Get ( "default_cooldown" ) . ( int ) ) )
2015-06-26 07:51:31 +02:00
}
2014-10-11 01:25:23 +02:00
if d . HasChange ( "desired_capacity" ) {
2015-07-28 22:29:46 +02:00
opts . DesiredCapacity = aws . Int64 ( int64 ( d . Get ( "desired_capacity" ) . ( int ) ) )
2014-10-11 01:25:23 +02:00
}
2015-02-18 00:48:15 +01:00
if d . HasChange ( "launch_configuration" ) {
2015-02-20 15:55:54 +01:00
opts . LaunchConfigurationName = aws . String ( d . Get ( "launch_configuration" ) . ( string ) )
2015-02-18 00:48:15 +01:00
}
2014-10-10 23:34:40 +02:00
if d . HasChange ( "min_size" ) {
2015-07-28 22:29:46 +02:00
opts . MinSize = aws . Int64 ( int64 ( d . Get ( "min_size" ) . ( int ) ) )
2014-07-15 23:20:54 +02:00
}
2014-10-10 23:34:40 +02:00
if d . HasChange ( "max_size" ) {
2015-07-28 22:29:46 +02:00
opts . MaxSize = aws . Int64 ( int64 ( d . Get ( "max_size" ) . ( int ) ) )
2014-07-15 23:20:54 +02:00
}
2015-05-07 01:34:20 +02:00
2015-04-26 05:00:04 +02:00
if d . HasChange ( "health_check_grace_period" ) {
2015-07-28 22:29:46 +02:00
opts . HealthCheckGracePeriod = aws . Int64 ( int64 ( d . Get ( "health_check_grace_period" ) . ( int ) ) )
2015-05-07 01:34:20 +02:00
}
2014-07-15 23:20:54 +02:00
2015-05-30 11:51:56 +02:00
if d . HasChange ( "health_check_type" ) {
2015-07-28 22:29:46 +02:00
opts . HealthCheckGracePeriod = aws . Int64 ( int64 ( d . Get ( "health_check_grace_period" ) . ( int ) ) )
2015-05-30 11:51:56 +02:00
opts . HealthCheckType = aws . String ( d . Get ( "health_check_type" ) . ( string ) )
}
2015-07-14 17:19:10 +02:00
if d . HasChange ( "vpc_zone_identifier" ) {
opts . VPCZoneIdentifier = expandVpcZoneIdentifiers ( d . Get ( "vpc_zone_identifier" ) . ( * schema . Set ) . List ( ) )
}
if d . HasChange ( "availability_zones" ) {
if v , ok := d . GetOk ( "availability_zones" ) ; ok && v . ( * schema . Set ) . Len ( ) > 0 {
opts . AvailabilityZones = expandStringList ( d . Get ( "availability_zones" ) . ( * schema . Set ) . List ( ) )
}
}
2015-07-29 23:44:02 +02:00
if d . HasChange ( "termination_policies" ) {
// If the termination policy is set to null, we need to explicitly set
// it back to "Default", or the API won't reset it for us.
// This means GetOk() will fail us on the zero check.
v := d . Get ( "termination_policies" )
if len ( v . ( [ ] interface { } ) ) > 0 {
opts . TerminationPolicies = expandStringList ( v . ( [ ] interface { } ) )
} else {
// Policies is a slice of string pointers, so build one.
// Maybe there's a better idiom for this?
log . Printf ( "[DEBUG] Explictly setting null termination policy to 'Default'" )
pol := "Default"
s := make ( [ ] * string , 1 , 1 )
s [ 0 ] = & pol
opts . TerminationPolicies = s
}
}
2015-05-07 01:54:59 +02:00
if err := setAutoscalingTags ( conn , d ) ; err != nil {
2015-03-03 23:36:25 +01:00
return err
} else {
d . SetPartial ( "tag" )
}
2014-07-15 23:20:54 +02:00
log . Printf ( "[DEBUG] AutoScaling Group update configuration: %#v" , opts )
2015-05-07 01:54:59 +02:00
_ , err := conn . UpdateAutoScalingGroup ( & opts )
2014-07-15 23:20:54 +02:00
if err != nil {
2014-10-10 23:34:40 +02:00
d . Partial ( true )
return fmt . Errorf ( "Error updating Autoscaling group: %s" , err )
2014-07-15 23:20:54 +02:00
}
2015-06-24 23:37:23 +02:00
if d . HasChange ( "load_balancers" ) {
o , n := d . GetChange ( "load_balancers" )
if o == nil {
o = new ( schema . Set )
}
if n == nil {
n = new ( schema . Set )
}
os := o . ( * schema . Set )
ns := n . ( * schema . Set )
remove := expandStringList ( os . Difference ( ns ) . List ( ) )
add := expandStringList ( ns . Difference ( os ) . List ( ) )
if len ( remove ) > 0 {
_ , err := conn . DetachLoadBalancers ( & autoscaling . DetachLoadBalancersInput {
AutoScalingGroupName : aws . String ( d . Id ( ) ) ,
LoadBalancerNames : remove ,
} )
if err != nil {
return fmt . Errorf ( "[WARN] Error updating Load Balancers for AutoScaling Group (%s), error: %s" , d . Id ( ) , err )
}
}
if len ( add ) > 0 {
_ , err := conn . AttachLoadBalancers ( & autoscaling . AttachLoadBalancersInput {
AutoScalingGroupName : aws . String ( d . Id ( ) ) ,
LoadBalancerNames : add ,
} )
if err != nil {
return fmt . Errorf ( "[WARN] Error updating Load Balancers for AutoScaling Group (%s), error: %s" , d . Id ( ) , err )
}
}
}
2014-10-10 23:34:40 +02:00
return resourceAwsAutoscalingGroupRead ( d , meta )
2014-07-10 01:00:11 +02:00
}
2014-10-10 23:34:40 +02:00
func resourceAwsAutoscalingGroupDelete ( d * schema . ResourceData , meta interface { } ) error {
2015-05-07 01:54:59 +02:00
conn := meta . ( * AWSClient ) . autoscalingconn
2014-07-10 01:00:11 +02:00
2014-10-18 05:10:52 +02:00
// Read the autoscaling group first. If it doesn't exist, we're done.
// We need the group in order to check if there are instances attached.
// If so, we need to remove those first.
g , err := getAwsAutoscalingGroup ( d , meta )
if err != nil {
return err
}
if g == nil {
return nil
}
2015-02-20 15:55:54 +01:00
if len ( g . Instances ) > 0 || * g . DesiredCapacity > 0 {
2014-10-18 05:10:52 +02:00
if err := resourceAwsAutoscalingGroupDrain ( d , meta ) ; err != nil {
return err
}
}
2014-10-10 23:34:40 +02:00
log . Printf ( "[DEBUG] AutoScaling Group destroy: %v" , d . Id ( ) )
2015-10-12 22:50:07 +02:00
deleteopts := autoscaling . DeleteAutoScalingGroupInput {
AutoScalingGroupName : aws . String ( d . Id ( ) ) ,
ForceDelete : aws . Bool ( d . Get ( "force_delete" ) . ( bool ) ) ,
2014-07-14 17:36:25 +02:00
}
2015-05-07 01:54:59 +02:00
// We retry the delete operation to handle InUse/InProgress errors coming
// from scaling operations. We should be able to sneak in a delete in between
// scaling operations within 5m.
err = resource . Retry ( 5 * time . Minute , func ( ) error {
if _ , err := conn . DeleteAutoScalingGroup ( & deleteopts ) ; err != nil {
2015-05-20 13:21:23 +02:00
if awserr , ok := err . ( awserr . Error ) ; ok {
switch awserr . Code ( ) {
2015-05-07 01:54:59 +02:00
case "InvalidGroup.NotFound" :
// Already gone? Sure!
return nil
case "ResourceInUse" , "ScalingActivityInProgress" :
// These are retryable
return awserr
}
}
// Didn't recognize the error, so shouldn't retry.
return resource . RetryError { Err : err }
2014-07-10 21:41:06 +02:00
}
2015-05-07 01:54:59 +02:00
// Successful delete
return nil
} )
if err != nil {
2014-07-10 21:41:06 +02:00
return err
}
2014-07-10 01:00:11 +02:00
2015-04-01 16:24:26 +02:00
return resource . Retry ( 5 * time . Minute , func ( ) error {
if g , _ = getAwsAutoscalingGroup ( d , meta ) ; g != nil {
return fmt . Errorf ( "Auto Scaling Group still exists" )
}
return nil
} )
2014-07-10 01:00:11 +02:00
}
2014-10-18 05:10:52 +02:00
func getAwsAutoscalingGroup (
d * schema . ResourceData ,
2015-05-29 09:55:59 +02:00
meta interface { } ) ( * autoscaling . Group , error ) {
2015-05-07 01:54:59 +02:00
conn := meta . ( * AWSClient ) . autoscalingconn
2014-07-10 01:00:11 +02:00
2015-04-15 22:30:35 +02:00
describeOpts := autoscaling . DescribeAutoScalingGroupsInput {
AutoScalingGroupNames : [ ] * string { aws . String ( d . Id ( ) ) } ,
2014-07-10 01:00:11 +02:00
}
2014-07-14 17:36:25 +02:00
log . Printf ( "[DEBUG] AutoScaling Group describe configuration: %#v" , describeOpts )
2015-05-07 01:54:59 +02:00
describeGroups , err := conn . DescribeAutoScalingGroups ( & describeOpts )
2014-07-10 01:00:11 +02:00
if err != nil {
2015-05-20 13:21:23 +02:00
autoscalingerr , ok := err . ( awserr . Error )
if ok && autoscalingerr . Code ( ) == "InvalidGroup.NotFound" {
2014-10-10 23:34:40 +02:00
d . SetId ( "" )
2014-10-18 05:10:52 +02:00
return nil , nil
2014-10-10 23:34:40 +02:00
}
2014-10-18 05:10:52 +02:00
return nil , fmt . Errorf ( "Error retrieving AutoScaling groups: %s" , err )
2014-07-10 01:00:11 +02:00
}
2014-12-10 02:11:50 +01:00
// Search for the autoscaling group
for idx , asc := range describeGroups . AutoScalingGroups {
2015-02-20 18:28:20 +01:00
if * asc . AutoScalingGroupName == d . Id ( ) {
2015-04-15 22:30:35 +02:00
return describeGroups . AutoScalingGroups [ idx ] , nil
2014-07-10 01:00:11 +02:00
}
}
2014-12-10 02:11:50 +01:00
// ASG not found
d . SetId ( "" )
return nil , nil
2014-10-18 05:10:52 +02:00
}
2014-07-10 01:00:11 +02:00
2014-10-18 05:10:52 +02:00
func resourceAwsAutoscalingGroupDrain ( d * schema . ResourceData , meta interface { } ) error {
2015-05-07 01:54:59 +02:00
conn := meta . ( * AWSClient ) . autoscalingconn
2014-07-15 18:31:49 +02:00
2015-10-12 22:50:07 +02:00
if d . Get ( "force_delete" ) . ( bool ) {
log . Printf ( "[DEBUG] Skipping ASG drain, force_delete was set." )
return nil
}
2014-10-18 05:10:52 +02:00
// First, set the capacity to zero so the group will drain
log . Printf ( "[DEBUG] Reducing autoscaling group capacity to zero" )
2015-04-15 22:30:35 +02:00
opts := autoscaling . UpdateAutoScalingGroupInput {
2015-02-20 18:28:20 +01:00
AutoScalingGroupName : aws . String ( d . Id ( ) ) ,
2015-07-28 22:29:46 +02:00
DesiredCapacity : aws . Int64 ( 0 ) ,
MinSize : aws . Int64 ( 0 ) ,
MaxSize : aws . Int64 ( 0 ) ,
2014-10-18 05:10:52 +02:00
}
2015-05-07 01:54:59 +02:00
if _ , err := conn . UpdateAutoScalingGroup ( & opts ) ; err != nil {
2014-10-18 05:10:52 +02:00
return fmt . Errorf ( "Error setting capacity to zero to drain: %s" , err )
}
// Next, wait for the autoscale group to drain
log . Printf ( "[DEBUG] Waiting for group to have zero instances" )
return resource . Retry ( 10 * time . Minute , func ( ) error {
g , err := getAwsAutoscalingGroup ( d , meta )
if err != nil {
2015-02-18 19:26:59 +01:00
return resource . RetryError { Err : err }
2014-10-18 05:10:52 +02:00
}
if g == nil {
return nil
}
if len ( g . Instances ) == 0 {
return nil
}
return fmt . Errorf ( "group still has %d instances" , len ( g . Instances ) )
} )
2014-07-15 18:31:49 +02:00
}
2015-05-07 01:34:20 +02:00
// Waits for a minimum number of healthy instances to show up as healthy in the
// ASG before continuing. Waits up to `waitForASGCapacityTimeout` for
// "desired_capacity", or "min_size" if desired capacity is not specified.
2015-05-14 19:45:21 +02:00
//
// If "min_elb_capacity" is specified, will also wait for that number of
// instances to show up InService in all attached ELBs. See "Waiting for
// Capacity" in docs for more discussion of the feature.
2015-05-07 01:34:20 +02:00
func waitForASGCapacity ( d * schema . ResourceData , meta interface { } ) error {
2015-05-14 19:45:21 +02:00
wantASG := d . Get ( "min_size" ) . ( int )
2015-05-07 01:34:20 +02:00
if v := d . Get ( "desired_capacity" ) . ( int ) ; v > 0 {
2015-05-14 19:45:21 +02:00
wantASG = v
2015-05-07 01:34:20 +02:00
}
2015-05-14 19:45:21 +02:00
wantELB := d . Get ( "min_elb_capacity" ) . ( int )
2015-09-08 20:15:30 +02:00
wait , err := time . ParseDuration ( d . Get ( "wait_for_capacity_timeout" ) . ( string ) )
if err != nil {
return err
}
if wait == 0 {
log . Printf ( "[DEBUG] Capacity timeout set to 0, skipping capacity waiting." )
return nil
}
log . Printf ( "[DEBUG] Waiting %s for capacity: %d ASG, %d ELB" ,
wait , wantASG , wantELB )
2015-05-07 01:34:20 +02:00
2015-09-08 20:15:30 +02:00
return resource . Retry ( wait , func ( ) error {
2015-05-07 01:34:20 +02:00
g , err := getAwsAutoscalingGroup ( d , meta )
if err != nil {
return resource . RetryError { Err : err }
}
if g == nil {
return nil
}
2015-05-14 19:45:21 +02:00
lbis , err := getLBInstanceStates ( g , meta )
if err != nil {
return resource . RetryError { Err : err }
}
haveASG := 0
haveELB := 0
2015-05-07 01:34:20 +02:00
for _ , i := range g . Instances {
2015-08-17 20:27:16 +02:00
if i . HealthStatus == nil || i . InstanceId == nil || i . LifecycleState == nil {
2015-05-14 19:45:21 +02:00
continue
}
if ! strings . EqualFold ( * i . HealthStatus , "Healthy" ) {
continue
}
if ! strings . EqualFold ( * i . LifecycleState , "InService" ) {
2015-05-07 01:34:20 +02:00
continue
}
2015-05-14 19:45:21 +02:00
haveASG ++
if wantELB > 0 {
inAllLbs := true
for _ , states := range lbis {
2015-08-17 20:27:16 +02:00
state , ok := states [ * i . InstanceId ]
2015-05-14 19:45:21 +02:00
if ! ok || ! strings . EqualFold ( state , "InService" ) {
inAllLbs = false
}
}
if inAllLbs {
haveELB ++
}
2015-05-07 01:34:20 +02:00
}
}
2015-05-14 19:45:21 +02:00
log . Printf ( "[DEBUG] %q Capacity: %d/%d ASG, %d/%d ELB" ,
d . Id ( ) , haveASG , wantASG , haveELB , wantELB )
2015-05-07 01:34:20 +02:00
2015-05-14 19:45:21 +02:00
if haveASG >= wantASG && haveELB >= wantELB {
2015-05-07 01:34:20 +02:00
return nil
}
2015-06-05 23:14:21 +02:00
return fmt . Errorf ( "Still need to wait for more healthy instances. This could mean instances failed to launch. See Scaling History for more information." )
2015-05-07 01:34:20 +02:00
} )
}
2015-05-14 19:45:21 +02:00
// Returns a mapping of the instance states of all the ELBs attached to the
// provided ASG.
//
// Nested like: lbName -> instanceId -> instanceState
2015-05-29 09:55:59 +02:00
func getLBInstanceStates ( g * autoscaling . Group , meta interface { } ) ( map [ string ] map [ string ] string , error ) {
2015-05-14 19:45:21 +02:00
lbInstanceStates := make ( map [ string ] map [ string ] string )
elbconn := meta . ( * AWSClient ) . elbconn
for _ , lbName := range g . LoadBalancerNames {
lbInstanceStates [ * lbName ] = make ( map [ string ] string )
opts := & elb . DescribeInstanceHealthInput { LoadBalancerName : lbName }
r , err := elbconn . DescribeInstanceHealth ( opts )
if err != nil {
return nil , err
}
for _ , is := range r . InstanceStates {
2015-08-17 20:27:16 +02:00
if is . InstanceId == nil || is . State == nil {
2015-05-14 19:45:21 +02:00
continue
}
2015-08-17 20:27:16 +02:00
lbInstanceStates [ * lbName ] [ * is . InstanceId ] = * is . State
2015-05-14 19:45:21 +02:00
}
}
return lbInstanceStates , nil
}
2015-07-14 17:19:10 +02:00
func expandVpcZoneIdentifiers ( list [ ] interface { } ) * string {
strs := make ( [ ] string , len ( list ) )
for _ , s := range list {
strs = append ( strs , s . ( string ) )
}
return aws . String ( strings . Join ( strs , "," ) )
}