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