github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/builtin/providers/aws/resource_aws_autoscaling_group.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/hashicorp/errwrap"
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  
    13  	"github.com/aws/aws-sdk-go/aws"
    14  	"github.com/aws/aws-sdk-go/aws/awserr"
    15  	"github.com/aws/aws-sdk-go/service/autoscaling"
    16  	"github.com/aws/aws-sdk-go/service/elb"
    17  	"github.com/aws/aws-sdk-go/service/elbv2"
    18  )
    19  
    20  func resourceAwsAutoscalingGroup() *schema.Resource {
    21  	return &schema.Resource{
    22  		Create: resourceAwsAutoscalingGroupCreate,
    23  		Read:   resourceAwsAutoscalingGroupRead,
    24  		Update: resourceAwsAutoscalingGroupUpdate,
    25  		Delete: resourceAwsAutoscalingGroupDelete,
    26  		Importer: &schema.ResourceImporter{
    27  			State: schema.ImportStatePassthrough,
    28  		},
    29  
    30  		Schema: map[string]*schema.Schema{
    31  			"name": &schema.Schema{
    32  				Type:          schema.TypeString,
    33  				Optional:      true,
    34  				Computed:      true,
    35  				ForceNew:      true,
    36  				ConflictsWith: []string{"name_prefix"},
    37  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    38  					// https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1862-L1873
    39  					value := v.(string)
    40  					if len(value) > 255 {
    41  						errors = append(errors, fmt.Errorf(
    42  							"%q cannot be longer than 255 characters", k))
    43  					}
    44  					return
    45  				},
    46  			},
    47  			"name_prefix": &schema.Schema{
    48  				Type:     schema.TypeString,
    49  				Optional: true,
    50  				ForceNew: true,
    51  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    52  					value := v.(string)
    53  					if len(value) > 229 {
    54  						errors = append(errors, fmt.Errorf(
    55  							"%q cannot be longer than 229 characters, name is limited to 255", k))
    56  					}
    57  					return
    58  				},
    59  			},
    60  
    61  			"launch_configuration": {
    62  				Type:     schema.TypeString,
    63  				Required: true,
    64  			},
    65  
    66  			"desired_capacity": {
    67  				Type:     schema.TypeInt,
    68  				Optional: true,
    69  				Computed: true,
    70  			},
    71  
    72  			"min_elb_capacity": {
    73  				Type:     schema.TypeInt,
    74  				Optional: true,
    75  			},
    76  
    77  			"min_size": {
    78  				Type:     schema.TypeInt,
    79  				Required: true,
    80  			},
    81  
    82  			"max_size": {
    83  				Type:     schema.TypeInt,
    84  				Required: true,
    85  			},
    86  
    87  			"default_cooldown": {
    88  				Type:     schema.TypeInt,
    89  				Optional: true,
    90  				Computed: true,
    91  			},
    92  
    93  			"force_delete": {
    94  				Type:     schema.TypeBool,
    95  				Optional: true,
    96  				Default:  false,
    97  			},
    98  
    99  			"health_check_grace_period": {
   100  				Type:     schema.TypeInt,
   101  				Optional: true,
   102  				Default:  300,
   103  			},
   104  
   105  			"health_check_type": {
   106  				Type:     schema.TypeString,
   107  				Optional: true,
   108  				Computed: true,
   109  			},
   110  
   111  			"availability_zones": {
   112  				Type:     schema.TypeSet,
   113  				Optional: true,
   114  				Computed: true,
   115  				Elem:     &schema.Schema{Type: schema.TypeString},
   116  				Set:      schema.HashString,
   117  			},
   118  
   119  			"placement_group": {
   120  				Type:     schema.TypeString,
   121  				Optional: true,
   122  			},
   123  
   124  			"load_balancers": {
   125  				Type:     schema.TypeSet,
   126  				Optional: true,
   127  				Computed: true,
   128  				Elem:     &schema.Schema{Type: schema.TypeString},
   129  				Set:      schema.HashString,
   130  			},
   131  
   132  			"vpc_zone_identifier": {
   133  				Type:     schema.TypeSet,
   134  				Optional: true,
   135  				Computed: true,
   136  				Elem:     &schema.Schema{Type: schema.TypeString},
   137  				Set:      schema.HashString,
   138  			},
   139  
   140  			"termination_policies": {
   141  				Type:     schema.TypeList,
   142  				Optional: true,
   143  				Elem:     &schema.Schema{Type: schema.TypeString},
   144  			},
   145  
   146  			"wait_for_capacity_timeout": {
   147  				Type:     schema.TypeString,
   148  				Optional: true,
   149  				Default:  "10m",
   150  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   151  					value := v.(string)
   152  					duration, err := time.ParseDuration(value)
   153  					if err != nil {
   154  						errors = append(errors, fmt.Errorf(
   155  							"%q cannot be parsed as a duration: %s", k, err))
   156  					}
   157  					if duration < 0 {
   158  						errors = append(errors, fmt.Errorf(
   159  							"%q must be greater than zero", k))
   160  					}
   161  					return
   162  				},
   163  			},
   164  
   165  			"wait_for_elb_capacity": {
   166  				Type:     schema.TypeInt,
   167  				Optional: true,
   168  			},
   169  
   170  			"enabled_metrics": {
   171  				Type:     schema.TypeSet,
   172  				Optional: true,
   173  				Elem:     &schema.Schema{Type: schema.TypeString},
   174  				Set:      schema.HashString,
   175  			},
   176  
   177  			"suspended_processes": {
   178  				Type:     schema.TypeSet,
   179  				Optional: true,
   180  				Elem:     &schema.Schema{Type: schema.TypeString},
   181  				Set:      schema.HashString,
   182  			},
   183  
   184  			"metrics_granularity": {
   185  				Type:     schema.TypeString,
   186  				Optional: true,
   187  				Default:  "1Minute",
   188  			},
   189  
   190  			"protect_from_scale_in": {
   191  				Type:     schema.TypeBool,
   192  				Optional: true,
   193  				Default:  false,
   194  			},
   195  
   196  			"target_group_arns": {
   197  				Type:     schema.TypeSet,
   198  				Optional: true,
   199  				Computed: true,
   200  				Elem:     &schema.Schema{Type: schema.TypeString},
   201  				Set:      schema.HashString,
   202  			},
   203  
   204  			"arn": {
   205  				Type:     schema.TypeString,
   206  				Computed: true,
   207  			},
   208  
   209  			"initial_lifecycle_hook": {
   210  				Type:     schema.TypeSet,
   211  				Optional: true,
   212  				Elem: &schema.Resource{
   213  					Schema: map[string]*schema.Schema{
   214  						"name": {
   215  							Type:     schema.TypeString,
   216  							Required: true,
   217  						},
   218  						"default_result": {
   219  							Type:     schema.TypeString,
   220  							Optional: true,
   221  							Computed: true,
   222  						},
   223  						"heartbeat_timeout": {
   224  							Type:     schema.TypeInt,
   225  							Optional: true,
   226  						},
   227  						"lifecycle_transition": {
   228  							Type:     schema.TypeString,
   229  							Required: true,
   230  						},
   231  						"notification_metadata": {
   232  							Type:     schema.TypeString,
   233  							Optional: true,
   234  						},
   235  						"notification_target_arn": {
   236  							Type:     schema.TypeString,
   237  							Optional: true,
   238  						},
   239  						"role_arn": {
   240  							Type:     schema.TypeString,
   241  							Optional: true,
   242  						},
   243  					},
   244  				},
   245  			},
   246  
   247  			"tag": autoscalingTagsSchema(),
   248  		},
   249  	}
   250  }
   251  
   252  func generatePutLifecycleHookInputs(asgName string, cfgs []interface{}) []autoscaling.PutLifecycleHookInput {
   253  	res := make([]autoscaling.PutLifecycleHookInput, 0, len(cfgs))
   254  
   255  	for _, raw := range cfgs {
   256  		cfg := raw.(map[string]interface{})
   257  
   258  		input := autoscaling.PutLifecycleHookInput{
   259  			AutoScalingGroupName: &asgName,
   260  			LifecycleHookName:    aws.String(cfg["name"].(string)),
   261  		}
   262  
   263  		if v, ok := cfg["default_result"]; ok && v.(string) != "" {
   264  			input.DefaultResult = aws.String(v.(string))
   265  		}
   266  
   267  		if v, ok := cfg["heartbeat_timeout"]; ok && v.(int) > 0 {
   268  			input.HeartbeatTimeout = aws.Int64(int64(v.(int)))
   269  		}
   270  
   271  		if v, ok := cfg["lifecycle_transition"]; ok && v.(string) != "" {
   272  			input.LifecycleTransition = aws.String(v.(string))
   273  		}
   274  
   275  		if v, ok := cfg["notification_metadata"]; ok && v.(string) != "" {
   276  			input.NotificationMetadata = aws.String(v.(string))
   277  		}
   278  
   279  		if v, ok := cfg["notification_target_arn"]; ok && v.(string) != "" {
   280  			input.NotificationTargetARN = aws.String(v.(string))
   281  		}
   282  
   283  		if v, ok := cfg["role_arn"]; ok && v.(string) != "" {
   284  			input.RoleARN = aws.String(v.(string))
   285  		}
   286  
   287  		res = append(res, input)
   288  	}
   289  
   290  	return res
   291  }
   292  
   293  func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) error {
   294  	conn := meta.(*AWSClient).autoscalingconn
   295  
   296  	var asgName string
   297  	if v, ok := d.GetOk("name"); ok {
   298  		asgName = v.(string)
   299  	} else {
   300  		if v, ok := d.GetOk("name_prefix"); ok {
   301  			asgName = resource.PrefixedUniqueId(v.(string))
   302  		} else {
   303  			asgName = resource.PrefixedUniqueId("tf-asg-")
   304  		}
   305  		d.Set("name", asgName)
   306  	}
   307  
   308  	createOpts := autoscaling.CreateAutoScalingGroupInput{
   309  		AutoScalingGroupName:             aws.String(asgName),
   310  		LaunchConfigurationName:          aws.String(d.Get("launch_configuration").(string)),
   311  		NewInstancesProtectedFromScaleIn: aws.Bool(d.Get("protect_from_scale_in").(bool)),
   312  	}
   313  	updateOpts := autoscaling.UpdateAutoScalingGroupInput{
   314  		AutoScalingGroupName: aws.String(asgName),
   315  	}
   316  
   317  	initialLifecycleHooks := d.Get("initial_lifecycle_hook").(*schema.Set).List()
   318  	twoPhases := len(initialLifecycleHooks) > 0
   319  
   320  	minSize := aws.Int64(int64(d.Get("min_size").(int)))
   321  	maxSize := aws.Int64(int64(d.Get("max_size").(int)))
   322  
   323  	if twoPhases {
   324  		createOpts.MinSize = aws.Int64(int64(0))
   325  		createOpts.MaxSize = aws.Int64(int64(0))
   326  
   327  		updateOpts.MinSize = minSize
   328  		updateOpts.MaxSize = maxSize
   329  
   330  		if v, ok := d.GetOk("desired_capacity"); ok {
   331  			updateOpts.DesiredCapacity = aws.Int64(int64(v.(int)))
   332  		}
   333  	} else {
   334  		createOpts.MinSize = minSize
   335  		createOpts.MaxSize = maxSize
   336  
   337  		if v, ok := d.GetOk("desired_capacity"); ok {
   338  			createOpts.DesiredCapacity = aws.Int64(int64(v.(int)))
   339  		}
   340  	}
   341  
   342  	// Availability Zones are optional if VPC Zone Identifer(s) are specified
   343  	if v, ok := d.GetOk("availability_zones"); ok && v.(*schema.Set).Len() > 0 {
   344  		createOpts.AvailabilityZones = expandStringList(v.(*schema.Set).List())
   345  	}
   346  
   347  	if v, ok := d.GetOk("tag"); ok {
   348  		createOpts.Tags = autoscalingTagsFromMap(
   349  			setToMapByKey(v.(*schema.Set), "key"), d.Get("name").(string))
   350  	}
   351  
   352  	if v, ok := d.GetOk("default_cooldown"); ok {
   353  		createOpts.DefaultCooldown = aws.Int64(int64(v.(int)))
   354  	}
   355  
   356  	if v, ok := d.GetOk("health_check_type"); ok && v.(string) != "" {
   357  		createOpts.HealthCheckType = aws.String(v.(string))
   358  	}
   359  
   360  	if v, ok := d.GetOk("health_check_grace_period"); ok {
   361  		createOpts.HealthCheckGracePeriod = aws.Int64(int64(v.(int)))
   362  	}
   363  
   364  	if v, ok := d.GetOk("placement_group"); ok {
   365  		createOpts.PlacementGroup = aws.String(v.(string))
   366  	}
   367  
   368  	if v, ok := d.GetOk("load_balancers"); ok && v.(*schema.Set).Len() > 0 {
   369  		createOpts.LoadBalancerNames = expandStringList(
   370  			v.(*schema.Set).List())
   371  	}
   372  
   373  	if v, ok := d.GetOk("vpc_zone_identifier"); ok && v.(*schema.Set).Len() > 0 {
   374  		createOpts.VPCZoneIdentifier = expandVpcZoneIdentifiers(v.(*schema.Set).List())
   375  	}
   376  
   377  	if v, ok := d.GetOk("termination_policies"); ok && len(v.([]interface{})) > 0 {
   378  		createOpts.TerminationPolicies = expandStringList(v.([]interface{}))
   379  	}
   380  
   381  	if v, ok := d.GetOk("target_group_arns"); ok && len(v.(*schema.Set).List()) > 0 {
   382  		createOpts.TargetGroupARNs = expandStringList(v.(*schema.Set).List())
   383  	}
   384  
   385  	log.Printf("[DEBUG] AutoScaling Group create configuration: %#v", createOpts)
   386  	_, err := conn.CreateAutoScalingGroup(&createOpts)
   387  	if err != nil {
   388  		return fmt.Errorf("Error creating AutoScaling Group: %s", err)
   389  	}
   390  
   391  	d.SetId(d.Get("name").(string))
   392  	log.Printf("[INFO] AutoScaling Group ID: %s", d.Id())
   393  
   394  	if twoPhases {
   395  		for _, hook := range generatePutLifecycleHookInputs(asgName, initialLifecycleHooks) {
   396  			if err = resourceAwsAutoscalingLifecycleHookPutOp(conn, &hook); err != nil {
   397  				return fmt.Errorf("Error creating initial lifecycle hooks: %s", err)
   398  			}
   399  		}
   400  
   401  		_, err = conn.UpdateAutoScalingGroup(&updateOpts)
   402  		if err != nil {
   403  			return fmt.Errorf("Error setting AutoScaling Group initial capacity: %s", err)
   404  		}
   405  	}
   406  
   407  	if err := waitForASGCapacity(d, meta, capacitySatisfiedCreate); err != nil {
   408  		return err
   409  	}
   410  
   411  	if _, ok := d.GetOk("suspended_processes"); ok {
   412  		suspendedProcessesErr := enableASGSuspendedProcesses(d, conn)
   413  		if suspendedProcessesErr != nil {
   414  			return suspendedProcessesErr
   415  		}
   416  	}
   417  
   418  	if _, ok := d.GetOk("enabled_metrics"); ok {
   419  		metricsErr := enableASGMetricsCollection(d, conn)
   420  		if metricsErr != nil {
   421  			return metricsErr
   422  		}
   423  	}
   424  
   425  	return resourceAwsAutoscalingGroupRead(d, meta)
   426  }
   427  
   428  func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) error {
   429  	conn := meta.(*AWSClient).autoscalingconn
   430  
   431  	g, err := getAwsAutoscalingGroup(d.Id(), conn)
   432  	if err != nil {
   433  		return err
   434  	}
   435  	if g == nil {
   436  		log.Printf("[INFO] Autoscaling Group %q not found", d.Id())
   437  		d.SetId("")
   438  		return nil
   439  	}
   440  
   441  	d.Set("availability_zones", flattenStringList(g.AvailabilityZones))
   442  	d.Set("default_cooldown", g.DefaultCooldown)
   443  	d.Set("arn", g.AutoScalingGroupARN)
   444  	d.Set("desired_capacity", g.DesiredCapacity)
   445  	d.Set("health_check_grace_period", g.HealthCheckGracePeriod)
   446  	d.Set("health_check_type", g.HealthCheckType)
   447  	d.Set("launch_configuration", g.LaunchConfigurationName)
   448  	d.Set("load_balancers", flattenStringList(g.LoadBalancerNames))
   449  	d.Set("target_group_arns", flattenStringList(g.TargetGroupARNs))
   450  
   451  	if err := d.Set("suspended_processes", flattenAsgSuspendedProcesses(g.SuspendedProcesses)); err != nil {
   452  		log.Printf("[WARN] Error setting suspended_processes for %q: %s", d.Id(), err)
   453  	}
   454  	if err := d.Set("target_group_arns", flattenStringList(g.TargetGroupARNs)); err != nil {
   455  		log.Printf("[ERR] Error setting target groups: %s", err)
   456  	}
   457  	d.Set("min_size", g.MinSize)
   458  	d.Set("max_size", g.MaxSize)
   459  	d.Set("placement_group", g.PlacementGroup)
   460  	d.Set("name", g.AutoScalingGroupName)
   461  	d.Set("tag", autoscalingTagDescriptionsToSlice(g.Tags))
   462  	d.Set("vpc_zone_identifier", strings.Split(*g.VPCZoneIdentifier, ","))
   463  	d.Set("protect_from_scale_in", g.NewInstancesProtectedFromScaleIn)
   464  
   465  	// If no termination polices are explicitly configured and the upstream state
   466  	// is only using the "Default" policy, clear the state to make it consistent
   467  	// with the default AWS create API behavior.
   468  	_, ok := d.GetOk("termination_policies")
   469  	if !ok && len(g.TerminationPolicies) == 1 && *g.TerminationPolicies[0] == "Default" {
   470  		d.Set("termination_policies", []interface{}{})
   471  	} else {
   472  		d.Set("termination_policies", flattenStringList(g.TerminationPolicies))
   473  	}
   474  
   475  	if g.EnabledMetrics != nil {
   476  		if err := d.Set("enabled_metrics", flattenAsgEnabledMetrics(g.EnabledMetrics)); err != nil {
   477  			log.Printf("[WARN] Error setting metrics for (%s): %s", d.Id(), err)
   478  		}
   479  		d.Set("metrics_granularity", g.EnabledMetrics[0].Granularity)
   480  	}
   481  
   482  	return nil
   483  }
   484  
   485  func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   486  	conn := meta.(*AWSClient).autoscalingconn
   487  	shouldWaitForCapacity := false
   488  
   489  	opts := autoscaling.UpdateAutoScalingGroupInput{
   490  		AutoScalingGroupName: aws.String(d.Id()),
   491  	}
   492  
   493  	opts.NewInstancesProtectedFromScaleIn = aws.Bool(d.Get("protect_from_scale_in").(bool))
   494  
   495  	if d.HasChange("default_cooldown") {
   496  		opts.DefaultCooldown = aws.Int64(int64(d.Get("default_cooldown").(int)))
   497  	}
   498  
   499  	if d.HasChange("desired_capacity") {
   500  		opts.DesiredCapacity = aws.Int64(int64(d.Get("desired_capacity").(int)))
   501  		shouldWaitForCapacity = true
   502  	}
   503  
   504  	if d.HasChange("launch_configuration") {
   505  		opts.LaunchConfigurationName = aws.String(d.Get("launch_configuration").(string))
   506  	}
   507  
   508  	if d.HasChange("min_size") {
   509  		opts.MinSize = aws.Int64(int64(d.Get("min_size").(int)))
   510  		shouldWaitForCapacity = true
   511  	}
   512  
   513  	if d.HasChange("max_size") {
   514  		opts.MaxSize = aws.Int64(int64(d.Get("max_size").(int)))
   515  	}
   516  
   517  	if d.HasChange("health_check_grace_period") {
   518  		opts.HealthCheckGracePeriod = aws.Int64(int64(d.Get("health_check_grace_period").(int)))
   519  	}
   520  
   521  	if d.HasChange("health_check_type") {
   522  		opts.HealthCheckGracePeriod = aws.Int64(int64(d.Get("health_check_grace_period").(int)))
   523  		opts.HealthCheckType = aws.String(d.Get("health_check_type").(string))
   524  	}
   525  
   526  	if d.HasChange("vpc_zone_identifier") {
   527  		opts.VPCZoneIdentifier = expandVpcZoneIdentifiers(d.Get("vpc_zone_identifier").(*schema.Set).List())
   528  	}
   529  
   530  	if d.HasChange("availability_zones") {
   531  		if v, ok := d.GetOk("availability_zones"); ok && v.(*schema.Set).Len() > 0 {
   532  			opts.AvailabilityZones = expandStringList(v.(*schema.Set).List())
   533  		}
   534  	}
   535  
   536  	if d.HasChange("placement_group") {
   537  		opts.PlacementGroup = aws.String(d.Get("placement_group").(string))
   538  	}
   539  
   540  	if d.HasChange("termination_policies") {
   541  		// If the termination policy is set to null, we need to explicitly set
   542  		// it back to "Default", or the API won't reset it for us.
   543  		if v, ok := d.GetOk("termination_policies"); ok && len(v.([]interface{})) > 0 {
   544  			opts.TerminationPolicies = expandStringList(v.([]interface{}))
   545  		} else {
   546  			log.Printf("[DEBUG] Explicitly setting null termination policy to 'Default'")
   547  			opts.TerminationPolicies = aws.StringSlice([]string{"Default"})
   548  		}
   549  	}
   550  
   551  	if err := setAutoscalingTags(conn, d); err != nil {
   552  		return err
   553  	} else {
   554  		d.SetPartial("tag")
   555  	}
   556  
   557  	log.Printf("[DEBUG] AutoScaling Group update configuration: %#v", opts)
   558  	_, err := conn.UpdateAutoScalingGroup(&opts)
   559  	if err != nil {
   560  		d.Partial(true)
   561  		return fmt.Errorf("Error updating Autoscaling group: %s", err)
   562  	}
   563  
   564  	if d.HasChange("load_balancers") {
   565  
   566  		o, n := d.GetChange("load_balancers")
   567  		if o == nil {
   568  			o = new(schema.Set)
   569  		}
   570  		if n == nil {
   571  			n = new(schema.Set)
   572  		}
   573  
   574  		os := o.(*schema.Set)
   575  		ns := n.(*schema.Set)
   576  		remove := expandStringList(os.Difference(ns).List())
   577  		add := expandStringList(ns.Difference(os).List())
   578  
   579  		if len(remove) > 0 {
   580  			_, err := conn.DetachLoadBalancers(&autoscaling.DetachLoadBalancersInput{
   581  				AutoScalingGroupName: aws.String(d.Id()),
   582  				LoadBalancerNames:    remove,
   583  			})
   584  			if err != nil {
   585  				return fmt.Errorf("[WARN] Error updating Load Balancers for AutoScaling Group (%s), error: %s", d.Id(), err)
   586  			}
   587  		}
   588  
   589  		if len(add) > 0 {
   590  			_, err := conn.AttachLoadBalancers(&autoscaling.AttachLoadBalancersInput{
   591  				AutoScalingGroupName: aws.String(d.Id()),
   592  				LoadBalancerNames:    add,
   593  			})
   594  			if err != nil {
   595  				return fmt.Errorf("[WARN] Error updating Load Balancers for AutoScaling Group (%s), error: %s", d.Id(), err)
   596  			}
   597  		}
   598  	}
   599  
   600  	if d.HasChange("target_group_arns") {
   601  
   602  		o, n := d.GetChange("target_group_arns")
   603  		if o == nil {
   604  			o = new(schema.Set)
   605  		}
   606  		if n == nil {
   607  			n = new(schema.Set)
   608  		}
   609  
   610  		os := o.(*schema.Set)
   611  		ns := n.(*schema.Set)
   612  		remove := expandStringList(os.Difference(ns).List())
   613  		add := expandStringList(ns.Difference(os).List())
   614  
   615  		if len(remove) > 0 {
   616  			_, err := conn.DetachLoadBalancerTargetGroups(&autoscaling.DetachLoadBalancerTargetGroupsInput{
   617  				AutoScalingGroupName: aws.String(d.Id()),
   618  				TargetGroupARNs:      remove,
   619  			})
   620  			if err != nil {
   621  				return fmt.Errorf("[WARN] Error updating Load Balancers Target Groups for AutoScaling Group (%s), error: %s", d.Id(), err)
   622  			}
   623  		}
   624  
   625  		if len(add) > 0 {
   626  			_, err := conn.AttachLoadBalancerTargetGroups(&autoscaling.AttachLoadBalancerTargetGroupsInput{
   627  				AutoScalingGroupName: aws.String(d.Id()),
   628  				TargetGroupARNs:      add,
   629  			})
   630  			if err != nil {
   631  				return fmt.Errorf("[WARN] Error updating Load Balancers Target Groups for AutoScaling Group (%s), error: %s", d.Id(), err)
   632  			}
   633  		}
   634  	}
   635  
   636  	if shouldWaitForCapacity {
   637  		if err := waitForASGCapacity(d, meta, capacitySatisfiedUpdate); err != nil {
   638  			return errwrap.Wrapf("Error waiting for AutoScaling Group Capacity: {{err}}", err)
   639  		}
   640  	}
   641  
   642  	if d.HasChange("enabled_metrics") {
   643  		if err := updateASGMetricsCollection(d, conn); err != nil {
   644  			return errwrap.Wrapf("Error updating AutoScaling Group Metrics collection: {{err}}", err)
   645  		}
   646  	}
   647  
   648  	if d.HasChange("suspended_processes") {
   649  		if err := updateASGSuspendedProcesses(d, conn); err != nil {
   650  			return errwrap.Wrapf("Error updating AutoScaling Group Suspended Processes: {{err}}", err)
   651  		}
   652  	}
   653  
   654  	return resourceAwsAutoscalingGroupRead(d, meta)
   655  }
   656  
   657  func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) error {
   658  	conn := meta.(*AWSClient).autoscalingconn
   659  
   660  	// Read the autoscaling group first. If it doesn't exist, we're done.
   661  	// We need the group in order to check if there are instances attached.
   662  	// If so, we need to remove those first.
   663  	g, err := getAwsAutoscalingGroup(d.Id(), conn)
   664  	if err != nil {
   665  		return err
   666  	}
   667  	if g == nil {
   668  		log.Printf("[INFO] Autoscaling Group %q not found", d.Id())
   669  		d.SetId("")
   670  		return nil
   671  	}
   672  	if len(g.Instances) > 0 || *g.DesiredCapacity > 0 {
   673  		if err := resourceAwsAutoscalingGroupDrain(d, meta); err != nil {
   674  			return err
   675  		}
   676  	}
   677  
   678  	log.Printf("[DEBUG] AutoScaling Group destroy: %v", d.Id())
   679  	deleteopts := autoscaling.DeleteAutoScalingGroupInput{
   680  		AutoScalingGroupName: aws.String(d.Id()),
   681  		ForceDelete:          aws.Bool(d.Get("force_delete").(bool)),
   682  	}
   683  
   684  	// We retry the delete operation to handle InUse/InProgress errors coming
   685  	// from scaling operations. We should be able to sneak in a delete in between
   686  	// scaling operations within 5m.
   687  	err = resource.Retry(5*time.Minute, func() *resource.RetryError {
   688  		if _, err := conn.DeleteAutoScalingGroup(&deleteopts); err != nil {
   689  			if awserr, ok := err.(awserr.Error); ok {
   690  				switch awserr.Code() {
   691  				case "InvalidGroup.NotFound":
   692  					// Already gone? Sure!
   693  					return nil
   694  				case "ResourceInUse", "ScalingActivityInProgress":
   695  					// These are retryable
   696  					return resource.RetryableError(awserr)
   697  				}
   698  			}
   699  			// Didn't recognize the error, so shouldn't retry.
   700  			return resource.NonRetryableError(err)
   701  		}
   702  		// Successful delete
   703  		return nil
   704  	})
   705  	if err != nil {
   706  		return err
   707  	}
   708  
   709  	return resource.Retry(5*time.Minute, func() *resource.RetryError {
   710  		if g, _ = getAwsAutoscalingGroup(d.Id(), conn); g != nil {
   711  			return resource.RetryableError(
   712  				fmt.Errorf("Auto Scaling Group still exists"))
   713  		}
   714  		return nil
   715  	})
   716  }
   717  
   718  func getAwsAutoscalingGroup(
   719  	asgName string,
   720  	conn *autoscaling.AutoScaling) (*autoscaling.Group, error) {
   721  
   722  	describeOpts := autoscaling.DescribeAutoScalingGroupsInput{
   723  		AutoScalingGroupNames: []*string{aws.String(asgName)},
   724  	}
   725  
   726  	log.Printf("[DEBUG] AutoScaling Group describe configuration: %#v", describeOpts)
   727  	describeGroups, err := conn.DescribeAutoScalingGroups(&describeOpts)
   728  	if err != nil {
   729  		autoscalingerr, ok := err.(awserr.Error)
   730  		if ok && autoscalingerr.Code() == "InvalidGroup.NotFound" {
   731  			return nil, nil
   732  		}
   733  
   734  		return nil, fmt.Errorf("Error retrieving AutoScaling groups: %s", err)
   735  	}
   736  
   737  	// Search for the autoscaling group
   738  	for idx, asc := range describeGroups.AutoScalingGroups {
   739  		if *asc.AutoScalingGroupName == asgName {
   740  			return describeGroups.AutoScalingGroups[idx], nil
   741  		}
   742  	}
   743  
   744  	return nil, nil
   745  }
   746  
   747  func resourceAwsAutoscalingGroupDrain(d *schema.ResourceData, meta interface{}) error {
   748  	conn := meta.(*AWSClient).autoscalingconn
   749  
   750  	if d.Get("force_delete").(bool) {
   751  		log.Printf("[DEBUG] Skipping ASG drain, force_delete was set.")
   752  		return nil
   753  	}
   754  
   755  	// First, set the capacity to zero so the group will drain
   756  	log.Printf("[DEBUG] Reducing autoscaling group capacity to zero")
   757  	opts := autoscaling.UpdateAutoScalingGroupInput{
   758  		AutoScalingGroupName: aws.String(d.Id()),
   759  		DesiredCapacity:      aws.Int64(0),
   760  		MinSize:              aws.Int64(0),
   761  		MaxSize:              aws.Int64(0),
   762  	}
   763  	if _, err := conn.UpdateAutoScalingGroup(&opts); err != nil {
   764  		return fmt.Errorf("Error setting capacity to zero to drain: %s", err)
   765  	}
   766  
   767  	// Next, wait for the autoscale group to drain
   768  	log.Printf("[DEBUG] Waiting for group to have zero instances")
   769  	return resource.Retry(10*time.Minute, func() *resource.RetryError {
   770  		g, err := getAwsAutoscalingGroup(d.Id(), conn)
   771  		if err != nil {
   772  			return resource.NonRetryableError(err)
   773  		}
   774  		if g == nil {
   775  			log.Printf("[INFO] Autoscaling Group %q not found", d.Id())
   776  			d.SetId("")
   777  			return nil
   778  		}
   779  
   780  		if len(g.Instances) == 0 {
   781  			return nil
   782  		}
   783  
   784  		return resource.RetryableError(
   785  			fmt.Errorf("group still has %d instances", len(g.Instances)))
   786  	})
   787  }
   788  
   789  func enableASGSuspendedProcesses(d *schema.ResourceData, conn *autoscaling.AutoScaling) error {
   790  	props := &autoscaling.ScalingProcessQuery{
   791  		AutoScalingGroupName: aws.String(d.Id()),
   792  		ScalingProcesses:     expandStringList(d.Get("suspended_processes").(*schema.Set).List()),
   793  	}
   794  
   795  	_, err := conn.SuspendProcesses(props)
   796  	if err != nil {
   797  		return err
   798  	}
   799  
   800  	return nil
   801  }
   802  
   803  func enableASGMetricsCollection(d *schema.ResourceData, conn *autoscaling.AutoScaling) error {
   804  	props := &autoscaling.EnableMetricsCollectionInput{
   805  		AutoScalingGroupName: aws.String(d.Id()),
   806  		Granularity:          aws.String(d.Get("metrics_granularity").(string)),
   807  		Metrics:              expandStringList(d.Get("enabled_metrics").(*schema.Set).List()),
   808  	}
   809  
   810  	log.Printf("[INFO] Enabling metrics collection for the ASG: %s", d.Id())
   811  	_, metricsErr := conn.EnableMetricsCollection(props)
   812  	if metricsErr != nil {
   813  		return metricsErr
   814  	}
   815  
   816  	return nil
   817  }
   818  
   819  func updateASGSuspendedProcesses(d *schema.ResourceData, conn *autoscaling.AutoScaling) error {
   820  	o, n := d.GetChange("suspended_processes")
   821  	if o == nil {
   822  		o = new(schema.Set)
   823  	}
   824  	if n == nil {
   825  		n = new(schema.Set)
   826  	}
   827  
   828  	os := o.(*schema.Set)
   829  	ns := n.(*schema.Set)
   830  
   831  	resumeProcesses := os.Difference(ns)
   832  	if resumeProcesses.Len() != 0 {
   833  		props := &autoscaling.ScalingProcessQuery{
   834  			AutoScalingGroupName: aws.String(d.Id()),
   835  			ScalingProcesses:     expandStringList(resumeProcesses.List()),
   836  		}
   837  
   838  		_, err := conn.ResumeProcesses(props)
   839  		if err != nil {
   840  			return fmt.Errorf("Error Resuming Processes for ASG %q: %s", d.Id(), err)
   841  		}
   842  	}
   843  
   844  	suspendedProcesses := ns.Difference(os)
   845  	if suspendedProcesses.Len() != 0 {
   846  		props := &autoscaling.ScalingProcessQuery{
   847  			AutoScalingGroupName: aws.String(d.Id()),
   848  			ScalingProcesses:     expandStringList(suspendedProcesses.List()),
   849  		}
   850  
   851  		_, err := conn.SuspendProcesses(props)
   852  		if err != nil {
   853  			return fmt.Errorf("Error Suspending Processes for ASG %q: %s", d.Id(), err)
   854  		}
   855  	}
   856  
   857  	return nil
   858  
   859  }
   860  
   861  func updateASGMetricsCollection(d *schema.ResourceData, conn *autoscaling.AutoScaling) error {
   862  
   863  	o, n := d.GetChange("enabled_metrics")
   864  	if o == nil {
   865  		o = new(schema.Set)
   866  	}
   867  	if n == nil {
   868  		n = new(schema.Set)
   869  	}
   870  
   871  	os := o.(*schema.Set)
   872  	ns := n.(*schema.Set)
   873  
   874  	disableMetrics := os.Difference(ns)
   875  	if disableMetrics.Len() != 0 {
   876  		props := &autoscaling.DisableMetricsCollectionInput{
   877  			AutoScalingGroupName: aws.String(d.Id()),
   878  			Metrics:              expandStringList(disableMetrics.List()),
   879  		}
   880  
   881  		_, err := conn.DisableMetricsCollection(props)
   882  		if err != nil {
   883  			return fmt.Errorf("Failure to Disable metrics collection types for ASG %s: %s", d.Id(), err)
   884  		}
   885  	}
   886  
   887  	enabledMetrics := ns.Difference(os)
   888  	if enabledMetrics.Len() != 0 {
   889  		props := &autoscaling.EnableMetricsCollectionInput{
   890  			AutoScalingGroupName: aws.String(d.Id()),
   891  			Metrics:              expandStringList(enabledMetrics.List()),
   892  			Granularity:          aws.String(d.Get("metrics_granularity").(string)),
   893  		}
   894  
   895  		_, err := conn.EnableMetricsCollection(props)
   896  		if err != nil {
   897  			return fmt.Errorf("Failure to Enable metrics collection types for ASG %s: %s", d.Id(), err)
   898  		}
   899  	}
   900  
   901  	return nil
   902  }
   903  
   904  // getELBInstanceStates returns a mapping of the instance states of all the ELBs attached to the
   905  // provided ASG.
   906  //
   907  // Note that this is the instance state function for ELB Classic.
   908  //
   909  // Nested like: lbName -> instanceId -> instanceState
   910  func getELBInstanceStates(g *autoscaling.Group, meta interface{}) (map[string]map[string]string, error) {
   911  	lbInstanceStates := make(map[string]map[string]string)
   912  	elbconn := meta.(*AWSClient).elbconn
   913  
   914  	for _, lbName := range g.LoadBalancerNames {
   915  		lbInstanceStates[*lbName] = make(map[string]string)
   916  		opts := &elb.DescribeInstanceHealthInput{LoadBalancerName: lbName}
   917  		r, err := elbconn.DescribeInstanceHealth(opts)
   918  		if err != nil {
   919  			return nil, err
   920  		}
   921  		for _, is := range r.InstanceStates {
   922  			if is.InstanceId == nil || is.State == nil {
   923  				continue
   924  			}
   925  			lbInstanceStates[*lbName][*is.InstanceId] = *is.State
   926  		}
   927  	}
   928  
   929  	return lbInstanceStates, nil
   930  }
   931  
   932  // getTargetGroupInstanceStates returns a mapping of the instance states of
   933  // all the ALB target groups attached to the provided ASG.
   934  //
   935  // Note that this is the instance state function for Application Load
   936  // Balancing (aka ELBv2).
   937  //
   938  // Nested like: targetGroupARN -> instanceId -> instanceState
   939  func getTargetGroupInstanceStates(g *autoscaling.Group, meta interface{}) (map[string]map[string]string, error) {
   940  	targetInstanceStates := make(map[string]map[string]string)
   941  	elbv2conn := meta.(*AWSClient).elbv2conn
   942  
   943  	for _, targetGroupARN := range g.TargetGroupARNs {
   944  		targetInstanceStates[*targetGroupARN] = make(map[string]string)
   945  		opts := &elbv2.DescribeTargetHealthInput{TargetGroupArn: targetGroupARN}
   946  		r, err := elbv2conn.DescribeTargetHealth(opts)
   947  		if err != nil {
   948  			return nil, err
   949  		}
   950  		for _, desc := range r.TargetHealthDescriptions {
   951  			if desc.Target == nil || desc.Target.Id == nil || desc.TargetHealth == nil || desc.TargetHealth.State == nil {
   952  				continue
   953  			}
   954  			targetInstanceStates[*targetGroupARN][*desc.Target.Id] = *desc.TargetHealth.State
   955  		}
   956  	}
   957  
   958  	return targetInstanceStates, nil
   959  }
   960  
   961  func expandVpcZoneIdentifiers(list []interface{}) *string {
   962  	strs := make([]string, len(list))
   963  	for _, s := range list {
   964  		strs = append(strs, s.(string))
   965  	}
   966  	return aws.String(strings.Join(strs, ","))
   967  }