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