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