github.com/markdia/terraform@v0.5.1-0.20150508012022-f1ae920aa970/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/awslabs/aws-sdk-go/aws"
    13  	"github.com/awslabs/aws-sdk-go/service/autoscaling"
    14  )
    15  
    16  func resourceAwsAutoscalingGroup() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceAwsAutoscalingGroupCreate,
    19  		Read:   resourceAwsAutoscalingGroupRead,
    20  		Update: resourceAwsAutoscalingGroupUpdate,
    21  		Delete: resourceAwsAutoscalingGroupDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"name": &schema.Schema{
    25  				Type:     schema.TypeString,
    26  				Required: true,
    27  				ForceNew: true,
    28  			},
    29  
    30  			"launch_configuration": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Required: true,
    33  			},
    34  
    35  			"desired_capacity": &schema.Schema{
    36  				Type:     schema.TypeInt,
    37  				Optional: true,
    38  				Computed: true,
    39  			},
    40  
    41  			"min_size": &schema.Schema{
    42  				Type:     schema.TypeInt,
    43  				Required: true,
    44  			},
    45  
    46  			"max_size": &schema.Schema{
    47  				Type:     schema.TypeInt,
    48  				Required: true,
    49  			},
    50  
    51  			"default_cooldown": &schema.Schema{
    52  				Type:     schema.TypeInt,
    53  				Optional: true,
    54  				Computed: true,
    55  				ForceNew: true,
    56  			},
    57  
    58  			"force_delete": &schema.Schema{
    59  				Type:     schema.TypeBool,
    60  				Optional: true,
    61  				Computed: true,
    62  				ForceNew: true,
    63  			},
    64  
    65  			"health_check_grace_period": &schema.Schema{
    66  				Type:     schema.TypeInt,
    67  				Optional: true,
    68  				Computed: true,
    69  			},
    70  
    71  			"health_check_type": &schema.Schema{
    72  				Type:     schema.TypeString,
    73  				Optional: true,
    74  				Computed: true,
    75  				ForceNew: true,
    76  			},
    77  
    78  			"availability_zones": &schema.Schema{
    79  				Type:     schema.TypeSet,
    80  				Required: true,
    81  				ForceNew: true,
    82  				Elem:     &schema.Schema{Type: schema.TypeString},
    83  				Set:      schema.HashString,
    84  			},
    85  
    86  			"load_balancers": &schema.Schema{
    87  				Type:     schema.TypeSet,
    88  				Optional: true,
    89  				ForceNew: true,
    90  				Elem:     &schema.Schema{Type: schema.TypeString},
    91  				Set:      schema.HashString,
    92  			},
    93  
    94  			"vpc_zone_identifier": &schema.Schema{
    95  				Type:     schema.TypeSet,
    96  				Optional: true,
    97  				Computed: true,
    98  				ForceNew: true,
    99  				Elem:     &schema.Schema{Type: schema.TypeString},
   100  				Set:      schema.HashString,
   101  			},
   102  
   103  			"termination_policies": &schema.Schema{
   104  				Type:     schema.TypeSet,
   105  				Optional: true,
   106  				Computed: true,
   107  				ForceNew: true,
   108  				Elem:     &schema.Schema{Type: schema.TypeString},
   109  				Set:      schema.HashString,
   110  			},
   111  
   112  			"tag": autoscalingTagsSchema(),
   113  		},
   114  	}
   115  }
   116  
   117  func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) error {
   118  	conn := meta.(*AWSClient).autoscalingconn
   119  
   120  	var autoScalingGroupOpts autoscaling.CreateAutoScalingGroupInput
   121  	autoScalingGroupOpts.AutoScalingGroupName = aws.String(d.Get("name").(string))
   122  	autoScalingGroupOpts.LaunchConfigurationName = aws.String(d.Get("launch_configuration").(string))
   123  	autoScalingGroupOpts.MinSize = aws.Long(int64(d.Get("min_size").(int)))
   124  	autoScalingGroupOpts.MaxSize = aws.Long(int64(d.Get("max_size").(int)))
   125  	autoScalingGroupOpts.AvailabilityZones = expandStringList(
   126  		d.Get("availability_zones").(*schema.Set).List())
   127  
   128  	if v, ok := d.GetOk("tag"); ok {
   129  		autoScalingGroupOpts.Tags = autoscalingTagsFromMap(
   130  			setToMapByKey(v.(*schema.Set), "key"), d.Get("name").(string))
   131  	}
   132  
   133  	if v, ok := d.GetOk("default_cooldown"); ok {
   134  		autoScalingGroupOpts.DefaultCooldown = aws.Long(int64(v.(int)))
   135  	}
   136  
   137  	if v, ok := d.GetOk("health_check_type"); ok && v.(string) != "" {
   138  		autoScalingGroupOpts.HealthCheckType = aws.String(v.(string))
   139  	}
   140  
   141  	if v, ok := d.GetOk("desired_capacity"); ok {
   142  		autoScalingGroupOpts.DesiredCapacity = aws.Long(int64(v.(int)))
   143  	}
   144  
   145  	if v, ok := d.GetOk("health_check_grace_period"); ok {
   146  		autoScalingGroupOpts.HealthCheckGracePeriod = aws.Long(int64(v.(int)))
   147  	}
   148  
   149  	if v, ok := d.GetOk("load_balancers"); ok && v.(*schema.Set).Len() > 0 {
   150  		autoScalingGroupOpts.LoadBalancerNames = expandStringList(
   151  			v.(*schema.Set).List())
   152  	}
   153  
   154  	if v, ok := d.GetOk("vpc_zone_identifier"); ok && v.(*schema.Set).Len() > 0 {
   155  		exp := expandStringList(v.(*schema.Set).List())
   156  		strs := make([]string, len(exp))
   157  		for _, s := range exp {
   158  			strs = append(strs, *s)
   159  		}
   160  		autoScalingGroupOpts.VPCZoneIdentifier = aws.String(strings.Join(strs, ","))
   161  	}
   162  
   163  	if v, ok := d.GetOk("termination_policies"); ok && v.(*schema.Set).Len() > 0 {
   164  		autoScalingGroupOpts.TerminationPolicies = expandStringList(
   165  			v.(*schema.Set).List())
   166  	}
   167  
   168  	log.Printf("[DEBUG] AutoScaling Group create configuration: %#v", autoScalingGroupOpts)
   169  	_, err := conn.CreateAutoScalingGroup(&autoScalingGroupOpts)
   170  	if err != nil {
   171  		return fmt.Errorf("Error creating Autoscaling Group: %s", err)
   172  	}
   173  
   174  	d.SetId(d.Get("name").(string))
   175  	log.Printf("[INFO] AutoScaling Group ID: %s", d.Id())
   176  
   177  	if err := waitForASGCapacity(d, meta); err != nil {
   178  		return err
   179  	}
   180  
   181  	return resourceAwsAutoscalingGroupRead(d, meta)
   182  }
   183  
   184  func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) error {
   185  	g, err := getAwsAutoscalingGroup(d, meta)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	if g == nil {
   190  		return nil
   191  	}
   192  
   193  	d.Set("availability_zones", g.AvailabilityZones)
   194  	d.Set("default_cooldown", g.DefaultCooldown)
   195  	d.Set("desired_capacity", g.DesiredCapacity)
   196  	d.Set("health_check_grace_period", g.HealthCheckGracePeriod)
   197  	d.Set("health_check_type", g.HealthCheckType)
   198  	d.Set("launch_configuration", g.LaunchConfigurationName)
   199  	d.Set("load_balancers", g.LoadBalancerNames)
   200  	d.Set("min_size", g.MinSize)
   201  	d.Set("max_size", g.MaxSize)
   202  	d.Set("name", g.AutoScalingGroupName)
   203  	d.Set("tag", g.Tags)
   204  	d.Set("vpc_zone_identifier", strings.Split(*g.VPCZoneIdentifier, ","))
   205  	d.Set("termination_policies", g.TerminationPolicies)
   206  
   207  	return nil
   208  }
   209  
   210  func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   211  	conn := meta.(*AWSClient).autoscalingconn
   212  
   213  	opts := autoscaling.UpdateAutoScalingGroupInput{
   214  		AutoScalingGroupName: aws.String(d.Id()),
   215  	}
   216  
   217  	if d.HasChange("desired_capacity") {
   218  		opts.DesiredCapacity = aws.Long(int64(d.Get("desired_capacity").(int)))
   219  	}
   220  
   221  	if d.HasChange("launch_configuration") {
   222  		opts.LaunchConfigurationName = aws.String(d.Get("launch_configuration").(string))
   223  	}
   224  
   225  	if d.HasChange("min_size") {
   226  		opts.MinSize = aws.Long(int64(d.Get("min_size").(int)))
   227  	}
   228  
   229  	if d.HasChange("max_size") {
   230  		opts.MaxSize = aws.Long(int64(d.Get("max_size").(int)))
   231  	}
   232  
   233  	if d.HasChange("health_check_grace_period") {
   234  		opts.HealthCheckGracePeriod = aws.Long(int64(d.Get("health_check_grace_period").(int)))
   235  	}
   236  
   237  	if err := setAutoscalingTags(conn, d); err != nil {
   238  		return err
   239  	} else {
   240  		d.SetPartial("tag")
   241  	}
   242  
   243  	log.Printf("[DEBUG] AutoScaling Group update configuration: %#v", opts)
   244  	_, err := conn.UpdateAutoScalingGroup(&opts)
   245  	if err != nil {
   246  		d.Partial(true)
   247  		return fmt.Errorf("Error updating Autoscaling group: %s", err)
   248  	}
   249  
   250  	return resourceAwsAutoscalingGroupRead(d, meta)
   251  }
   252  
   253  func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) error {
   254  	conn := meta.(*AWSClient).autoscalingconn
   255  
   256  	// Read the autoscaling group first. If it doesn't exist, we're done.
   257  	// We need the group in order to check if there are instances attached.
   258  	// If so, we need to remove those first.
   259  	g, err := getAwsAutoscalingGroup(d, meta)
   260  	if err != nil {
   261  		return err
   262  	}
   263  	if g == nil {
   264  		return nil
   265  	}
   266  	if len(g.Instances) > 0 || *g.DesiredCapacity > 0 {
   267  		if err := resourceAwsAutoscalingGroupDrain(d, meta); err != nil {
   268  			return err
   269  		}
   270  	}
   271  
   272  	log.Printf("[DEBUG] AutoScaling Group destroy: %v", d.Id())
   273  	deleteopts := autoscaling.DeleteAutoScalingGroupInput{AutoScalingGroupName: aws.String(d.Id())}
   274  
   275  	// You can force an autoscaling group to delete
   276  	// even if it's in the process of scaling a resource.
   277  	// Normally, you would set the min-size and max-size to 0,0
   278  	// and then delete the group. This bypasses that and leaves
   279  	// resources potentially dangling.
   280  	if d.Get("force_delete").(bool) {
   281  		deleteopts.ForceDelete = aws.Boolean(true)
   282  	}
   283  
   284  	// We retry the delete operation to handle InUse/InProgress errors coming
   285  	// from scaling operations. We should be able to sneak in a delete in between
   286  	// scaling operations within 5m.
   287  	err = resource.Retry(5*time.Minute, func() error {
   288  		if _, err := conn.DeleteAutoScalingGroup(&deleteopts); err != nil {
   289  			if awserr, ok := err.(aws.APIError); ok {
   290  				switch awserr.Code {
   291  				case "InvalidGroup.NotFound":
   292  					// Already gone? Sure!
   293  					return nil
   294  				case "ResourceInUse", "ScalingActivityInProgress":
   295  					// These are retryable
   296  					return awserr
   297  				}
   298  			}
   299  			// Didn't recognize the error, so shouldn't retry.
   300  			return resource.RetryError{Err: err}
   301  		}
   302  		// Successful delete
   303  		return nil
   304  	})
   305  	if err != nil {
   306  		return err
   307  	}
   308  
   309  	return resource.Retry(5*time.Minute, func() error {
   310  		if g, _ = getAwsAutoscalingGroup(d, meta); g != nil {
   311  			return fmt.Errorf("Auto Scaling Group still exists")
   312  		}
   313  		return nil
   314  	})
   315  }
   316  
   317  func getAwsAutoscalingGroup(
   318  	d *schema.ResourceData,
   319  	meta interface{}) (*autoscaling.AutoScalingGroup, error) {
   320  	conn := meta.(*AWSClient).autoscalingconn
   321  
   322  	describeOpts := autoscaling.DescribeAutoScalingGroupsInput{
   323  		AutoScalingGroupNames: []*string{aws.String(d.Id())},
   324  	}
   325  
   326  	log.Printf("[DEBUG] AutoScaling Group describe configuration: %#v", describeOpts)
   327  	describeGroups, err := conn.DescribeAutoScalingGroups(&describeOpts)
   328  	if err != nil {
   329  		autoscalingerr, ok := err.(aws.APIError)
   330  		if ok && autoscalingerr.Code == "InvalidGroup.NotFound" {
   331  			d.SetId("")
   332  			return nil, nil
   333  		}
   334  
   335  		return nil, fmt.Errorf("Error retrieving AutoScaling groups: %s", err)
   336  	}
   337  
   338  	// Search for the autoscaling group
   339  	for idx, asc := range describeGroups.AutoScalingGroups {
   340  		if *asc.AutoScalingGroupName == d.Id() {
   341  			return describeGroups.AutoScalingGroups[idx], nil
   342  		}
   343  	}
   344  
   345  	// ASG not found
   346  	d.SetId("")
   347  	return nil, nil
   348  }
   349  
   350  func resourceAwsAutoscalingGroupDrain(d *schema.ResourceData, meta interface{}) error {
   351  	conn := meta.(*AWSClient).autoscalingconn
   352  
   353  	// First, set the capacity to zero so the group will drain
   354  	log.Printf("[DEBUG] Reducing autoscaling group capacity to zero")
   355  	opts := autoscaling.UpdateAutoScalingGroupInput{
   356  		AutoScalingGroupName: aws.String(d.Id()),
   357  		DesiredCapacity:      aws.Long(0),
   358  		MinSize:              aws.Long(0),
   359  		MaxSize:              aws.Long(0),
   360  	}
   361  	if _, err := conn.UpdateAutoScalingGroup(&opts); err != nil {
   362  		return fmt.Errorf("Error setting capacity to zero to drain: %s", err)
   363  	}
   364  
   365  	// Next, wait for the autoscale group to drain
   366  	log.Printf("[DEBUG] Waiting for group to have zero instances")
   367  	return resource.Retry(10*time.Minute, func() error {
   368  		g, err := getAwsAutoscalingGroup(d, meta)
   369  		if err != nil {
   370  			return resource.RetryError{Err: err}
   371  		}
   372  		if g == nil {
   373  			return nil
   374  		}
   375  
   376  		if len(g.Instances) == 0 {
   377  			return nil
   378  		}
   379  
   380  		return fmt.Errorf("group still has %d instances", len(g.Instances))
   381  	})
   382  }
   383  
   384  var waitForASGCapacityTimeout = 10 * time.Minute
   385  
   386  // Waits for a minimum number of healthy instances to show up as healthy in the
   387  // ASG before continuing. Waits up to `waitForASGCapacityTimeout` for
   388  // "desired_capacity", or "min_size" if desired capacity is not specified.
   389  func waitForASGCapacity(d *schema.ResourceData, meta interface{}) error {
   390  	waitFor := d.Get("min_size").(int)
   391  	if v := d.Get("desired_capacity").(int); v > 0 {
   392  		waitFor = v
   393  	}
   394  
   395  	log.Printf("[DEBUG] Waiting for group to have %d healthy instances", waitFor)
   396  	return resource.Retry(waitForASGCapacityTimeout, func() error {
   397  		g, err := getAwsAutoscalingGroup(d, meta)
   398  		if err != nil {
   399  			return resource.RetryError{Err: err}
   400  		}
   401  		if g == nil {
   402  			return nil
   403  		}
   404  
   405  		healthy := 0
   406  		for _, i := range g.Instances {
   407  			if i.HealthStatus == nil {
   408  				continue
   409  			}
   410  			if strings.EqualFold(*i.HealthStatus, "Healthy") {
   411  				healthy++
   412  			}
   413  		}
   414  
   415  		log.Printf(
   416  			"[DEBUG] %q has %d/%d healthy instances", d.Id(), healthy, waitFor)
   417  
   418  		if healthy >= waitFor {
   419  			return nil
   420  		}
   421  
   422  		return fmt.Errorf("Waiting for healthy instances: %d/%d", healthy, waitFor)
   423  	})
   424  }