github.com/anuaimi/terraform@v0.6.4-0.20150904235404-2bf9aec61da8/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/terraform/helper/resource"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  
    12  	"github.com/aws/aws-sdk-go/aws"
    13  	"github.com/aws/aws-sdk-go/aws/awserr"
    14  	"github.com/aws/aws-sdk-go/service/autoscaling"
    15  	"github.com/aws/aws-sdk-go/service/elb"
    16  )
    17  
    18  func resourceAwsAutoscalingGroup() *schema.Resource {
    19  	return &schema.Resource{
    20  		Create: resourceAwsAutoscalingGroupCreate,
    21  		Read:   resourceAwsAutoscalingGroupRead,
    22  		Update: resourceAwsAutoscalingGroupUpdate,
    23  		Delete: resourceAwsAutoscalingGroupDelete,
    24  
    25  		Schema: map[string]*schema.Schema{
    26  			"name": &schema.Schema{
    27  				Type:     schema.TypeString,
    28  				Required: true,
    29  				ForceNew: true,
    30  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    31  					// https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1862-L1873
    32  					value := v.(string)
    33  					if len(value) > 255 {
    34  						errors = append(errors, fmt.Errorf(
    35  							"%q cannot be longer than 255 characters", k))
    36  					}
    37  					return
    38  				},
    39  			},
    40  
    41  			"launch_configuration": &schema.Schema{
    42  				Type:     schema.TypeString,
    43  				Required: true,
    44  			},
    45  
    46  			"desired_capacity": &schema.Schema{
    47  				Type:     schema.TypeInt,
    48  				Optional: true,
    49  				Computed: true,
    50  			},
    51  
    52  			"min_elb_capacity": &schema.Schema{
    53  				Type:     schema.TypeInt,
    54  				Optional: true,
    55  			},
    56  
    57  			"min_size": &schema.Schema{
    58  				Type:     schema.TypeInt,
    59  				Required: true,
    60  			},
    61  
    62  			"max_size": &schema.Schema{
    63  				Type:     schema.TypeInt,
    64  				Required: true,
    65  			},
    66  
    67  			"default_cooldown": &schema.Schema{
    68  				Type:     schema.TypeInt,
    69  				Optional: true,
    70  				Computed: true,
    71  			},
    72  
    73  			"force_delete": &schema.Schema{
    74  				Type:     schema.TypeBool,
    75  				Optional: true,
    76  				Computed: true,
    77  				ForceNew: true,
    78  			},
    79  
    80  			"health_check_grace_period": &schema.Schema{
    81  				Type:     schema.TypeInt,
    82  				Optional: true,
    83  				Computed: true,
    84  			},
    85  
    86  			"health_check_type": &schema.Schema{
    87  				Type:     schema.TypeString,
    88  				Optional: true,
    89  				Computed: true,
    90  			},
    91  
    92  			"availability_zones": &schema.Schema{
    93  				Type:     schema.TypeSet,
    94  				Optional: true,
    95  				Elem:     &schema.Schema{Type: schema.TypeString},
    96  				Set:      schema.HashString,
    97  			},
    98  
    99  			"load_balancers": &schema.Schema{
   100  				Type:     schema.TypeSet,
   101  				Optional: true,
   102  				Elem:     &schema.Schema{Type: schema.TypeString},
   103  				Set:      schema.HashString,
   104  			},
   105  
   106  			"vpc_zone_identifier": &schema.Schema{
   107  				Type:     schema.TypeSet,
   108  				Optional: true,
   109  				Computed: true,
   110  				Elem:     &schema.Schema{Type: schema.TypeString},
   111  				Set:      schema.HashString,
   112  			},
   113  
   114  			"termination_policies": &schema.Schema{
   115  				Type:     schema.TypeSet,
   116  				Optional: true,
   117  				Computed: true,
   118  				ForceNew: true,
   119  				Elem:     &schema.Schema{Type: schema.TypeString},
   120  				Set:      schema.HashString,
   121  			},
   122  
   123  			"tag": autoscalingTagsSchema(),
   124  		},
   125  	}
   126  }
   127  
   128  func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) error {
   129  	conn := meta.(*AWSClient).autoscalingconn
   130  
   131  	var autoScalingGroupOpts autoscaling.CreateAutoScalingGroupInput
   132  	autoScalingGroupOpts.AutoScalingGroupName = aws.String(d.Get("name").(string))
   133  	autoScalingGroupOpts.LaunchConfigurationName = aws.String(d.Get("launch_configuration").(string))
   134  	autoScalingGroupOpts.MinSize = aws.Int64(int64(d.Get("min_size").(int)))
   135  	autoScalingGroupOpts.MaxSize = aws.Int64(int64(d.Get("max_size").(int)))
   136  
   137  	// Availability Zones are optional if VPC Zone Identifer(s) are specified
   138  	if v, ok := d.GetOk("availability_zones"); ok && v.(*schema.Set).Len() > 0 {
   139  		autoScalingGroupOpts.AvailabilityZones = expandStringList(v.(*schema.Set).List())
   140  	}
   141  
   142  	if v, ok := d.GetOk("tag"); ok {
   143  		autoScalingGroupOpts.Tags = autoscalingTagsFromMap(
   144  			setToMapByKey(v.(*schema.Set), "key"), d.Get("name").(string))
   145  	}
   146  
   147  	if v, ok := d.GetOk("default_cooldown"); ok {
   148  		autoScalingGroupOpts.DefaultCooldown = aws.Int64(int64(v.(int)))
   149  	}
   150  
   151  	if v, ok := d.GetOk("health_check_type"); ok && v.(string) != "" {
   152  		autoScalingGroupOpts.HealthCheckType = aws.String(v.(string))
   153  	}
   154  
   155  	if v, ok := d.GetOk("desired_capacity"); ok {
   156  		autoScalingGroupOpts.DesiredCapacity = aws.Int64(int64(v.(int)))
   157  	}
   158  
   159  	if v, ok := d.GetOk("health_check_grace_period"); ok {
   160  		autoScalingGroupOpts.HealthCheckGracePeriod = aws.Int64(int64(v.(int)))
   161  	}
   162  
   163  	if v, ok := d.GetOk("load_balancers"); ok && v.(*schema.Set).Len() > 0 {
   164  		autoScalingGroupOpts.LoadBalancerNames = expandStringList(
   165  			v.(*schema.Set).List())
   166  	}
   167  
   168  	if v, ok := d.GetOk("vpc_zone_identifier"); ok && v.(*schema.Set).Len() > 0 {
   169  		autoScalingGroupOpts.VPCZoneIdentifier = expandVpcZoneIdentifiers(v.(*schema.Set).List())
   170  	}
   171  
   172  	if v, ok := d.GetOk("termination_policies"); ok && v.(*schema.Set).Len() > 0 {
   173  		autoScalingGroupOpts.TerminationPolicies = expandStringList(
   174  			v.(*schema.Set).List())
   175  	}
   176  
   177  	log.Printf("[DEBUG] AutoScaling Group create configuration: %#v", autoScalingGroupOpts)
   178  	_, err := conn.CreateAutoScalingGroup(&autoScalingGroupOpts)
   179  	if err != nil {
   180  		return fmt.Errorf("Error creating Autoscaling Group: %s", err)
   181  	}
   182  
   183  	d.SetId(d.Get("name").(string))
   184  	log.Printf("[INFO] AutoScaling Group ID: %s", d.Id())
   185  
   186  	if err := waitForASGCapacity(d, meta); err != nil {
   187  		return err
   188  	}
   189  
   190  	return resourceAwsAutoscalingGroupRead(d, meta)
   191  }
   192  
   193  func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) error {
   194  	g, err := getAwsAutoscalingGroup(d, meta)
   195  	if err != nil {
   196  		return err
   197  	}
   198  	if g == nil {
   199  		return nil
   200  	}
   201  
   202  	d.Set("availability_zones", g.AvailabilityZones)
   203  	d.Set("default_cooldown", g.DefaultCooldown)
   204  	d.Set("desired_capacity", g.DesiredCapacity)
   205  	d.Set("health_check_grace_period", g.HealthCheckGracePeriod)
   206  	d.Set("health_check_type", g.HealthCheckType)
   207  	d.Set("launch_configuration", g.LaunchConfigurationName)
   208  	d.Set("load_balancers", g.LoadBalancerNames)
   209  	d.Set("min_size", g.MinSize)
   210  	d.Set("max_size", g.MaxSize)
   211  	d.Set("name", g.AutoScalingGroupName)
   212  	d.Set("tag", g.Tags)
   213  	d.Set("vpc_zone_identifier", strings.Split(*g.VPCZoneIdentifier, ","))
   214  	d.Set("termination_policies", g.TerminationPolicies)
   215  
   216  	return nil
   217  }
   218  
   219  func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   220  	conn := meta.(*AWSClient).autoscalingconn
   221  
   222  	opts := autoscaling.UpdateAutoScalingGroupInput{
   223  		AutoScalingGroupName: aws.String(d.Id()),
   224  	}
   225  
   226  	if d.HasChange("default_cooldown") {
   227  		opts.DefaultCooldown = aws.Int64(int64(d.Get("default_cooldown").(int)))
   228  	}
   229  
   230  	if d.HasChange("desired_capacity") {
   231  		opts.DesiredCapacity = aws.Int64(int64(d.Get("desired_capacity").(int)))
   232  	}
   233  
   234  	if d.HasChange("launch_configuration") {
   235  		opts.LaunchConfigurationName = aws.String(d.Get("launch_configuration").(string))
   236  	}
   237  
   238  	if d.HasChange("min_size") {
   239  		opts.MinSize = aws.Int64(int64(d.Get("min_size").(int)))
   240  	}
   241  
   242  	if d.HasChange("max_size") {
   243  		opts.MaxSize = aws.Int64(int64(d.Get("max_size").(int)))
   244  	}
   245  
   246  	if d.HasChange("health_check_grace_period") {
   247  		opts.HealthCheckGracePeriod = aws.Int64(int64(d.Get("health_check_grace_period").(int)))
   248  	}
   249  
   250  	if d.HasChange("health_check_type") {
   251  		opts.HealthCheckGracePeriod = aws.Int64(int64(d.Get("health_check_grace_period").(int)))
   252  		opts.HealthCheckType = aws.String(d.Get("health_check_type").(string))
   253  	}
   254  
   255  	if d.HasChange("vpc_zone_identifier") {
   256  		opts.VPCZoneIdentifier = expandVpcZoneIdentifiers(d.Get("vpc_zone_identifier").(*schema.Set).List())
   257  	}
   258  
   259  	if d.HasChange("availability_zones") {
   260  		if v, ok := d.GetOk("availability_zones"); ok && v.(*schema.Set).Len() > 0 {
   261  			opts.AvailabilityZones = expandStringList(d.Get("availability_zones").(*schema.Set).List())
   262  		}
   263  	}
   264  
   265  	if err := setAutoscalingTags(conn, d); err != nil {
   266  		return err
   267  	} else {
   268  		d.SetPartial("tag")
   269  	}
   270  
   271  	log.Printf("[DEBUG] AutoScaling Group update configuration: %#v", opts)
   272  	_, err := conn.UpdateAutoScalingGroup(&opts)
   273  	if err != nil {
   274  		d.Partial(true)
   275  		return fmt.Errorf("Error updating Autoscaling group: %s", err)
   276  	}
   277  
   278  	if d.HasChange("load_balancers") {
   279  
   280  		o, n := d.GetChange("load_balancers")
   281  		if o == nil {
   282  			o = new(schema.Set)
   283  		}
   284  		if n == nil {
   285  			n = new(schema.Set)
   286  		}
   287  
   288  		os := o.(*schema.Set)
   289  		ns := n.(*schema.Set)
   290  		remove := expandStringList(os.Difference(ns).List())
   291  		add := expandStringList(ns.Difference(os).List())
   292  
   293  		if len(remove) > 0 {
   294  			_, err := conn.DetachLoadBalancers(&autoscaling.DetachLoadBalancersInput{
   295  				AutoScalingGroupName: aws.String(d.Id()),
   296  				LoadBalancerNames:    remove,
   297  			})
   298  			if err != nil {
   299  				return fmt.Errorf("[WARN] Error updating Load Balancers for AutoScaling Group (%s), error: %s", d.Id(), err)
   300  			}
   301  		}
   302  
   303  		if len(add) > 0 {
   304  			_, err := conn.AttachLoadBalancers(&autoscaling.AttachLoadBalancersInput{
   305  				AutoScalingGroupName: aws.String(d.Id()),
   306  				LoadBalancerNames:    add,
   307  			})
   308  			if err != nil {
   309  				return fmt.Errorf("[WARN] Error updating Load Balancers for AutoScaling Group (%s), error: %s", d.Id(), err)
   310  			}
   311  		}
   312  	}
   313  
   314  	return resourceAwsAutoscalingGroupRead(d, meta)
   315  }
   316  
   317  func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) error {
   318  	conn := meta.(*AWSClient).autoscalingconn
   319  
   320  	// Read the autoscaling group first. If it doesn't exist, we're done.
   321  	// We need the group in order to check if there are instances attached.
   322  	// If so, we need to remove those first.
   323  	g, err := getAwsAutoscalingGroup(d, meta)
   324  	if err != nil {
   325  		return err
   326  	}
   327  	if g == nil {
   328  		return nil
   329  	}
   330  	if len(g.Instances) > 0 || *g.DesiredCapacity > 0 {
   331  		if err := resourceAwsAutoscalingGroupDrain(d, meta); err != nil {
   332  			return err
   333  		}
   334  	}
   335  
   336  	log.Printf("[DEBUG] AutoScaling Group destroy: %v", d.Id())
   337  	deleteopts := autoscaling.DeleteAutoScalingGroupInput{AutoScalingGroupName: aws.String(d.Id())}
   338  
   339  	// You can force an autoscaling group to delete
   340  	// even if it's in the process of scaling a resource.
   341  	// Normally, you would set the min-size and max-size to 0,0
   342  	// and then delete the group. This bypasses that and leaves
   343  	// resources potentially dangling.
   344  	if d.Get("force_delete").(bool) {
   345  		deleteopts.ForceDelete = aws.Bool(true)
   346  	}
   347  
   348  	// We retry the delete operation to handle InUse/InProgress errors coming
   349  	// from scaling operations. We should be able to sneak in a delete in between
   350  	// scaling operations within 5m.
   351  	err = resource.Retry(5*time.Minute, func() error {
   352  		if _, err := conn.DeleteAutoScalingGroup(&deleteopts); err != nil {
   353  			if awserr, ok := err.(awserr.Error); ok {
   354  				switch awserr.Code() {
   355  				case "InvalidGroup.NotFound":
   356  					// Already gone? Sure!
   357  					return nil
   358  				case "ResourceInUse", "ScalingActivityInProgress":
   359  					// These are retryable
   360  					return awserr
   361  				}
   362  			}
   363  			// Didn't recognize the error, so shouldn't retry.
   364  			return resource.RetryError{Err: err}
   365  		}
   366  		// Successful delete
   367  		return nil
   368  	})
   369  	if err != nil {
   370  		return err
   371  	}
   372  
   373  	return resource.Retry(5*time.Minute, func() error {
   374  		if g, _ = getAwsAutoscalingGroup(d, meta); g != nil {
   375  			return fmt.Errorf("Auto Scaling Group still exists")
   376  		}
   377  		return nil
   378  	})
   379  }
   380  
   381  func getAwsAutoscalingGroup(
   382  	d *schema.ResourceData,
   383  	meta interface{}) (*autoscaling.Group, error) {
   384  	conn := meta.(*AWSClient).autoscalingconn
   385  
   386  	describeOpts := autoscaling.DescribeAutoScalingGroupsInput{
   387  		AutoScalingGroupNames: []*string{aws.String(d.Id())},
   388  	}
   389  
   390  	log.Printf("[DEBUG] AutoScaling Group describe configuration: %#v", describeOpts)
   391  	describeGroups, err := conn.DescribeAutoScalingGroups(&describeOpts)
   392  	if err != nil {
   393  		autoscalingerr, ok := err.(awserr.Error)
   394  		if ok && autoscalingerr.Code() == "InvalidGroup.NotFound" {
   395  			d.SetId("")
   396  			return nil, nil
   397  		}
   398  
   399  		return nil, fmt.Errorf("Error retrieving AutoScaling groups: %s", err)
   400  	}
   401  
   402  	// Search for the autoscaling group
   403  	for idx, asc := range describeGroups.AutoScalingGroups {
   404  		if *asc.AutoScalingGroupName == d.Id() {
   405  			return describeGroups.AutoScalingGroups[idx], nil
   406  		}
   407  	}
   408  
   409  	// ASG not found
   410  	d.SetId("")
   411  	return nil, nil
   412  }
   413  
   414  func resourceAwsAutoscalingGroupDrain(d *schema.ResourceData, meta interface{}) error {
   415  	conn := meta.(*AWSClient).autoscalingconn
   416  
   417  	// First, set the capacity to zero so the group will drain
   418  	log.Printf("[DEBUG] Reducing autoscaling group capacity to zero")
   419  	opts := autoscaling.UpdateAutoScalingGroupInput{
   420  		AutoScalingGroupName: aws.String(d.Id()),
   421  		DesiredCapacity:      aws.Int64(0),
   422  		MinSize:              aws.Int64(0),
   423  		MaxSize:              aws.Int64(0),
   424  	}
   425  	if _, err := conn.UpdateAutoScalingGroup(&opts); err != nil {
   426  		return fmt.Errorf("Error setting capacity to zero to drain: %s", err)
   427  	}
   428  
   429  	// Next, wait for the autoscale group to drain
   430  	log.Printf("[DEBUG] Waiting for group to have zero instances")
   431  	return resource.Retry(10*time.Minute, func() error {
   432  		g, err := getAwsAutoscalingGroup(d, meta)
   433  		if err != nil {
   434  			return resource.RetryError{Err: err}
   435  		}
   436  		if g == nil {
   437  			return nil
   438  		}
   439  
   440  		if len(g.Instances) == 0 {
   441  			return nil
   442  		}
   443  
   444  		return fmt.Errorf("group still has %d instances", len(g.Instances))
   445  	})
   446  }
   447  
   448  var waitForASGCapacityTimeout = 10 * time.Minute
   449  
   450  // Waits for a minimum number of healthy instances to show up as healthy in the
   451  // ASG before continuing. Waits up to `waitForASGCapacityTimeout` for
   452  // "desired_capacity", or "min_size" if desired capacity is not specified.
   453  //
   454  // If "min_elb_capacity" is specified, will also wait for that number of
   455  // instances to show up InService in all attached ELBs. See "Waiting for
   456  // Capacity" in docs for more discussion of the feature.
   457  func waitForASGCapacity(d *schema.ResourceData, meta interface{}) error {
   458  	wantASG := d.Get("min_size").(int)
   459  	if v := d.Get("desired_capacity").(int); v > 0 {
   460  		wantASG = v
   461  	}
   462  	wantELB := d.Get("min_elb_capacity").(int)
   463  
   464  	log.Printf("[DEBUG] Waiting for capacity: %d ASG, %d ELB", wantASG, wantELB)
   465  
   466  	return resource.Retry(waitForASGCapacityTimeout, func() error {
   467  		g, err := getAwsAutoscalingGroup(d, meta)
   468  		if err != nil {
   469  			return resource.RetryError{Err: err}
   470  		}
   471  		if g == nil {
   472  			return nil
   473  		}
   474  		lbis, err := getLBInstanceStates(g, meta)
   475  		if err != nil {
   476  			return resource.RetryError{Err: err}
   477  		}
   478  
   479  		haveASG := 0
   480  		haveELB := 0
   481  
   482  		for _, i := range g.Instances {
   483  			if i.HealthStatus == nil || i.InstanceId == nil || i.LifecycleState == nil {
   484  				continue
   485  			}
   486  
   487  			if !strings.EqualFold(*i.HealthStatus, "Healthy") {
   488  				continue
   489  			}
   490  
   491  			if !strings.EqualFold(*i.LifecycleState, "InService") {
   492  				continue
   493  			}
   494  
   495  			haveASG++
   496  
   497  			if wantELB > 0 {
   498  				inAllLbs := true
   499  				for _, states := range lbis {
   500  					state, ok := states[*i.InstanceId]
   501  					if !ok || !strings.EqualFold(state, "InService") {
   502  						inAllLbs = false
   503  					}
   504  				}
   505  				if inAllLbs {
   506  					haveELB++
   507  				}
   508  			}
   509  		}
   510  
   511  		log.Printf("[DEBUG] %q Capacity: %d/%d ASG, %d/%d ELB",
   512  			d.Id(), haveASG, wantASG, haveELB, wantELB)
   513  
   514  		if haveASG >= wantASG && haveELB >= wantELB {
   515  			return nil
   516  		}
   517  
   518  		return fmt.Errorf("Still need to wait for more healthy instances. This could mean instances failed to launch. See Scaling History for more information.")
   519  	})
   520  }
   521  
   522  // Returns a mapping of the instance states of all the ELBs attached to the
   523  // provided ASG.
   524  //
   525  // Nested like: lbName -> instanceId -> instanceState
   526  func getLBInstanceStates(g *autoscaling.Group, meta interface{}) (map[string]map[string]string, error) {
   527  	lbInstanceStates := make(map[string]map[string]string)
   528  	elbconn := meta.(*AWSClient).elbconn
   529  
   530  	for _, lbName := range g.LoadBalancerNames {
   531  		lbInstanceStates[*lbName] = make(map[string]string)
   532  		opts := &elb.DescribeInstanceHealthInput{LoadBalancerName: lbName}
   533  		r, err := elbconn.DescribeInstanceHealth(opts)
   534  		if err != nil {
   535  			return nil, err
   536  		}
   537  		for _, is := range r.InstanceStates {
   538  			if is.InstanceId == nil || is.State == nil {
   539  				continue
   540  			}
   541  			lbInstanceStates[*lbName][*is.InstanceId] = *is.State
   542  		}
   543  	}
   544  
   545  	return lbInstanceStates, nil
   546  }
   547  
   548  func expandVpcZoneIdentifiers(list []interface{}) *string {
   549  	strs := make([]string, len(list))
   550  	for _, s := range list {
   551  		strs = append(strs, s.(string))
   552  	}
   553  	return aws.String(strings.Join(strs, ","))
   554  }