github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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/errwrap" 10 "github.com/hashicorp/terraform/helper/resource" 11 "github.com/hashicorp/terraform/helper/schema" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/awserr" 15 "github.com/aws/aws-sdk-go/service/autoscaling" 16 "github.com/aws/aws-sdk-go/service/elb" 17 "github.com/aws/aws-sdk-go/service/elbv2" 18 ) 19 20 func resourceAwsAutoscalingGroup() *schema.Resource { 21 return &schema.Resource{ 22 Create: resourceAwsAutoscalingGroupCreate, 23 Read: resourceAwsAutoscalingGroupRead, 24 Update: resourceAwsAutoscalingGroupUpdate, 25 Delete: resourceAwsAutoscalingGroupDelete, 26 Importer: &schema.ResourceImporter{ 27 State: schema.ImportStatePassthrough, 28 }, 29 30 Schema: map[string]*schema.Schema{ 31 "name": &schema.Schema{ 32 Type: schema.TypeString, 33 Optional: true, 34 Computed: true, 35 ForceNew: true, 36 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 37 // https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1862-L1873 38 value := v.(string) 39 if len(value) > 255 { 40 errors = append(errors, fmt.Errorf( 41 "%q cannot be longer than 255 characters", k)) 42 } 43 return 44 }, 45 }, 46 47 "launch_configuration": &schema.Schema{ 48 Type: schema.TypeString, 49 Required: true, 50 }, 51 52 "desired_capacity": &schema.Schema{ 53 Type: schema.TypeInt, 54 Optional: true, 55 Computed: true, 56 }, 57 58 "min_elb_capacity": &schema.Schema{ 59 Type: schema.TypeInt, 60 Optional: true, 61 }, 62 63 "min_size": &schema.Schema{ 64 Type: schema.TypeInt, 65 Required: true, 66 }, 67 68 "max_size": &schema.Schema{ 69 Type: schema.TypeInt, 70 Required: true, 71 }, 72 73 "default_cooldown": &schema.Schema{ 74 Type: schema.TypeInt, 75 Optional: true, 76 Computed: true, 77 }, 78 79 "force_delete": &schema.Schema{ 80 Type: schema.TypeBool, 81 Optional: true, 82 Default: false, 83 }, 84 85 "health_check_grace_period": &schema.Schema{ 86 Type: schema.TypeInt, 87 Optional: true, 88 Default: 300, 89 }, 90 91 "health_check_type": &schema.Schema{ 92 Type: schema.TypeString, 93 Optional: true, 94 Computed: true, 95 }, 96 97 "availability_zones": &schema.Schema{ 98 Type: schema.TypeSet, 99 Optional: true, 100 Computed: true, 101 Elem: &schema.Schema{Type: schema.TypeString}, 102 Set: schema.HashString, 103 }, 104 105 "placement_group": &schema.Schema{ 106 Type: schema.TypeString, 107 Optional: true, 108 }, 109 110 "load_balancers": &schema.Schema{ 111 Type: schema.TypeSet, 112 Optional: true, 113 Computed: true, 114 Elem: &schema.Schema{Type: schema.TypeString}, 115 Set: schema.HashString, 116 }, 117 118 "vpc_zone_identifier": &schema.Schema{ 119 Type: schema.TypeSet, 120 Optional: true, 121 Computed: true, 122 Elem: &schema.Schema{Type: schema.TypeString}, 123 Set: schema.HashString, 124 }, 125 126 "termination_policies": &schema.Schema{ 127 Type: schema.TypeList, 128 Optional: true, 129 Elem: &schema.Schema{Type: schema.TypeString}, 130 }, 131 132 "wait_for_capacity_timeout": &schema.Schema{ 133 Type: schema.TypeString, 134 Optional: true, 135 Default: "10m", 136 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 137 value := v.(string) 138 duration, err := time.ParseDuration(value) 139 if err != nil { 140 errors = append(errors, fmt.Errorf( 141 "%q cannot be parsed as a duration: %s", k, err)) 142 } 143 if duration < 0 { 144 errors = append(errors, fmt.Errorf( 145 "%q must be greater than zero", k)) 146 } 147 return 148 }, 149 }, 150 151 "wait_for_elb_capacity": &schema.Schema{ 152 Type: schema.TypeInt, 153 Optional: true, 154 }, 155 156 "enabled_metrics": &schema.Schema{ 157 Type: schema.TypeSet, 158 Optional: true, 159 Elem: &schema.Schema{Type: schema.TypeString}, 160 Set: schema.HashString, 161 }, 162 163 "suspended_processes": { 164 Type: schema.TypeSet, 165 Optional: true, 166 Elem: &schema.Schema{Type: schema.TypeString}, 167 Set: schema.HashString, 168 }, 169 170 "metrics_granularity": &schema.Schema{ 171 Type: schema.TypeString, 172 Optional: true, 173 Default: "1Minute", 174 }, 175 176 "protect_from_scale_in": &schema.Schema{ 177 Type: schema.TypeBool, 178 Optional: true, 179 Default: false, 180 }, 181 182 "target_group_arns": &schema.Schema{ 183 Type: schema.TypeSet, 184 Optional: true, 185 Elem: &schema.Schema{Type: schema.TypeString}, 186 Set: schema.HashString, 187 }, 188 189 "arn": &schema.Schema{ 190 Type: schema.TypeString, 191 Computed: true, 192 }, 193 194 "initial_lifecycle_hook": &schema.Schema{ 195 Type: schema.TypeSet, 196 Optional: true, 197 Elem: &schema.Resource{ 198 Schema: map[string]*schema.Schema{ 199 "name": { 200 Type: schema.TypeString, 201 Required: true, 202 }, 203 "default_result": { 204 Type: schema.TypeString, 205 Optional: true, 206 Computed: true, 207 }, 208 "heartbeat_timeout": { 209 Type: schema.TypeInt, 210 Optional: true, 211 }, 212 "lifecycle_transition": { 213 Type: schema.TypeString, 214 Required: true, 215 }, 216 "notification_metadata": { 217 Type: schema.TypeString, 218 Optional: true, 219 }, 220 "notification_target_arn": { 221 Type: schema.TypeString, 222 Optional: true, 223 }, 224 "role_arn": { 225 Type: schema.TypeString, 226 Optional: true, 227 }, 228 }, 229 }, 230 }, 231 232 "tag": autoscalingTagsSchema(), 233 }, 234 } 235 } 236 237 func generatePutLifecycleHookInputs(asgName string, cfgs []interface{}) []autoscaling.PutLifecycleHookInput { 238 res := make([]autoscaling.PutLifecycleHookInput, 0, len(cfgs)) 239 240 for _, raw := range cfgs { 241 cfg := raw.(map[string]interface{}) 242 243 input := autoscaling.PutLifecycleHookInput{ 244 AutoScalingGroupName: &asgName, 245 LifecycleHookName: aws.String(cfg["name"].(string)), 246 } 247 248 if v, ok := cfg["default_result"]; ok && v.(string) != "" { 249 input.DefaultResult = aws.String(v.(string)) 250 } 251 252 if v, ok := cfg["heartbeat_timeout"]; ok && v.(int) > 0 { 253 input.HeartbeatTimeout = aws.Int64(int64(v.(int))) 254 } 255 256 if v, ok := cfg["lifecycle_transition"]; ok && v.(string) != "" { 257 input.LifecycleTransition = aws.String(v.(string)) 258 } 259 260 if v, ok := cfg["notification_metadata"]; ok && v.(string) != "" { 261 input.NotificationMetadata = aws.String(v.(string)) 262 } 263 264 if v, ok := cfg["notification_target_arn"]; ok && v.(string) != "" { 265 input.NotificationTargetARN = aws.String(v.(string)) 266 } 267 268 if v, ok := cfg["role_arn"]; ok && v.(string) != "" { 269 input.RoleARN = aws.String(v.(string)) 270 } 271 272 res = append(res, input) 273 } 274 275 return res 276 } 277 278 func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) error { 279 conn := meta.(*AWSClient).autoscalingconn 280 281 var asgName string 282 if v, ok := d.GetOk("name"); ok { 283 asgName = v.(string) 284 } else { 285 asgName = resource.PrefixedUniqueId("tf-asg-") 286 d.Set("name", asgName) 287 } 288 289 createOpts := autoscaling.CreateAutoScalingGroupInput{ 290 AutoScalingGroupName: aws.String(asgName), 291 LaunchConfigurationName: aws.String(d.Get("launch_configuration").(string)), 292 NewInstancesProtectedFromScaleIn: aws.Bool(d.Get("protect_from_scale_in").(bool)), 293 } 294 updateOpts := autoscaling.UpdateAutoScalingGroupInput{ 295 AutoScalingGroupName: aws.String(asgName), 296 } 297 298 initialLifecycleHooks := d.Get("initial_lifecycle_hook").(*schema.Set).List() 299 twoPhases := len(initialLifecycleHooks) > 0 300 301 minSize := aws.Int64(int64(d.Get("min_size").(int))) 302 maxSize := aws.Int64(int64(d.Get("max_size").(int))) 303 304 if twoPhases { 305 createOpts.MinSize = aws.Int64(int64(0)) 306 createOpts.MaxSize = aws.Int64(int64(0)) 307 308 updateOpts.MinSize = minSize 309 updateOpts.MaxSize = maxSize 310 311 if v, ok := d.GetOk("desired_capacity"); ok { 312 updateOpts.DesiredCapacity = aws.Int64(int64(v.(int))) 313 } 314 } else { 315 createOpts.MinSize = minSize 316 createOpts.MaxSize = maxSize 317 318 if v, ok := d.GetOk("desired_capacity"); ok { 319 createOpts.DesiredCapacity = aws.Int64(int64(v.(int))) 320 } 321 } 322 323 // Availability Zones are optional if VPC Zone Identifer(s) are specified 324 if v, ok := d.GetOk("availability_zones"); ok && v.(*schema.Set).Len() > 0 { 325 createOpts.AvailabilityZones = expandStringList(v.(*schema.Set).List()) 326 } 327 328 if v, ok := d.GetOk("tag"); ok { 329 createOpts.Tags = autoscalingTagsFromMap( 330 setToMapByKey(v.(*schema.Set), "key"), d.Get("name").(string)) 331 } 332 333 if v, ok := d.GetOk("default_cooldown"); ok { 334 createOpts.DefaultCooldown = aws.Int64(int64(v.(int))) 335 } 336 337 if v, ok := d.GetOk("health_check_type"); ok && v.(string) != "" { 338 createOpts.HealthCheckType = aws.String(v.(string)) 339 } 340 341 if v, ok := d.GetOk("health_check_grace_period"); ok { 342 createOpts.HealthCheckGracePeriod = aws.Int64(int64(v.(int))) 343 } 344 345 if v, ok := d.GetOk("placement_group"); ok { 346 createOpts.PlacementGroup = aws.String(v.(string)) 347 } 348 349 if v, ok := d.GetOk("load_balancers"); ok && v.(*schema.Set).Len() > 0 { 350 createOpts.LoadBalancerNames = expandStringList( 351 v.(*schema.Set).List()) 352 } 353 354 if v, ok := d.GetOk("vpc_zone_identifier"); ok && v.(*schema.Set).Len() > 0 { 355 createOpts.VPCZoneIdentifier = expandVpcZoneIdentifiers(v.(*schema.Set).List()) 356 } 357 358 if v, ok := d.GetOk("termination_policies"); ok && len(v.([]interface{})) > 0 { 359 createOpts.TerminationPolicies = expandStringList(v.([]interface{})) 360 } 361 362 if v, ok := d.GetOk("target_group_arns"); ok && len(v.(*schema.Set).List()) > 0 { 363 createOpts.TargetGroupARNs = expandStringList(v.(*schema.Set).List()) 364 } 365 366 log.Printf("[DEBUG] AutoScaling Group create configuration: %#v", createOpts) 367 _, err := conn.CreateAutoScalingGroup(&createOpts) 368 if err != nil { 369 return fmt.Errorf("Error creating AutoScaling Group: %s", err) 370 } 371 372 d.SetId(d.Get("name").(string)) 373 log.Printf("[INFO] AutoScaling Group ID: %s", d.Id()) 374 375 if twoPhases { 376 for _, hook := range generatePutLifecycleHookInputs(asgName, initialLifecycleHooks) { 377 if err = resourceAwsAutoscalingLifecycleHookPutOp(conn, &hook); err != nil { 378 return fmt.Errorf("Error creating initial lifecycle hooks: %s", err) 379 } 380 } 381 382 _, err = conn.UpdateAutoScalingGroup(&updateOpts) 383 if err != nil { 384 return fmt.Errorf("Error setting AutoScaling Group initial capacity: %s", err) 385 } 386 } 387 388 if err := waitForASGCapacity(d, meta, capacitySatisfiedCreate); err != nil { 389 return err 390 } 391 392 if _, ok := d.GetOk("suspended_processes"); ok { 393 suspendedProcessesErr := enableASGSuspendedProcesses(d, conn) 394 if suspendedProcessesErr != nil { 395 return suspendedProcessesErr 396 } 397 } 398 399 if _, ok := d.GetOk("enabled_metrics"); ok { 400 metricsErr := enableASGMetricsCollection(d, conn) 401 if metricsErr != nil { 402 return metricsErr 403 } 404 } 405 406 return resourceAwsAutoscalingGroupRead(d, meta) 407 } 408 409 func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) error { 410 conn := meta.(*AWSClient).autoscalingconn 411 412 g, err := getAwsAutoscalingGroup(d.Id(), conn) 413 if err != nil { 414 return err 415 } 416 if g == nil { 417 log.Printf("[INFO] Autoscaling Group %q not found", d.Id()) 418 d.SetId("") 419 return nil 420 } 421 422 d.Set("availability_zones", flattenStringList(g.AvailabilityZones)) 423 d.Set("default_cooldown", g.DefaultCooldown) 424 d.Set("arn", g.AutoScalingGroupARN) 425 d.Set("desired_capacity", g.DesiredCapacity) 426 d.Set("health_check_grace_period", g.HealthCheckGracePeriod) 427 d.Set("health_check_type", g.HealthCheckType) 428 d.Set("launch_configuration", g.LaunchConfigurationName) 429 d.Set("load_balancers", flattenStringList(g.LoadBalancerNames)) 430 if err := d.Set("suspended_processes", flattenAsgSuspendedProcesses(g.SuspendedProcesses)); err != nil { 431 log.Printf("[WARN] Error setting suspended_processes for %q: %s", d.Id(), err) 432 } 433 if err := d.Set("target_group_arns", flattenStringList(g.TargetGroupARNs)); err != nil { 434 log.Printf("[ERR] Error setting target groups: %s", err) 435 } 436 d.Set("min_size", g.MinSize) 437 d.Set("max_size", g.MaxSize) 438 d.Set("placement_group", g.PlacementGroup) 439 d.Set("name", g.AutoScalingGroupName) 440 d.Set("tag", autoscalingTagDescriptionsToSlice(g.Tags)) 441 d.Set("vpc_zone_identifier", strings.Split(*g.VPCZoneIdentifier, ",")) 442 d.Set("protect_from_scale_in", g.NewInstancesProtectedFromScaleIn) 443 444 // If no termination polices are explicitly configured and the upstream state 445 // is only using the "Default" policy, clear the state to make it consistent 446 // with the default AWS create API behavior. 447 _, ok := d.GetOk("termination_policies") 448 if !ok && len(g.TerminationPolicies) == 1 && *g.TerminationPolicies[0] == "Default" { 449 d.Set("termination_policies", []interface{}{}) 450 } else { 451 d.Set("termination_policies", flattenStringList(g.TerminationPolicies)) 452 } 453 454 if g.EnabledMetrics != nil { 455 if err := d.Set("enabled_metrics", flattenAsgEnabledMetrics(g.EnabledMetrics)); err != nil { 456 log.Printf("[WARN] Error setting metrics for (%s): %s", d.Id(), err) 457 } 458 d.Set("metrics_granularity", g.EnabledMetrics[0].Granularity) 459 } 460 461 return nil 462 } 463 464 func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) error { 465 conn := meta.(*AWSClient).autoscalingconn 466 shouldWaitForCapacity := false 467 468 opts := autoscaling.UpdateAutoScalingGroupInput{ 469 AutoScalingGroupName: aws.String(d.Id()), 470 } 471 472 opts.NewInstancesProtectedFromScaleIn = aws.Bool(d.Get("protect_from_scale_in").(bool)) 473 474 if d.HasChange("default_cooldown") { 475 opts.DefaultCooldown = aws.Int64(int64(d.Get("default_cooldown").(int))) 476 } 477 478 if d.HasChange("desired_capacity") { 479 opts.DesiredCapacity = aws.Int64(int64(d.Get("desired_capacity").(int))) 480 shouldWaitForCapacity = true 481 } 482 483 if d.HasChange("launch_configuration") { 484 opts.LaunchConfigurationName = aws.String(d.Get("launch_configuration").(string)) 485 } 486 487 if d.HasChange("min_size") { 488 opts.MinSize = aws.Int64(int64(d.Get("min_size").(int))) 489 shouldWaitForCapacity = true 490 } 491 492 if d.HasChange("max_size") { 493 opts.MaxSize = aws.Int64(int64(d.Get("max_size").(int))) 494 } 495 496 if d.HasChange("health_check_grace_period") { 497 opts.HealthCheckGracePeriod = aws.Int64(int64(d.Get("health_check_grace_period").(int))) 498 } 499 500 if d.HasChange("health_check_type") { 501 opts.HealthCheckGracePeriod = aws.Int64(int64(d.Get("health_check_grace_period").(int))) 502 opts.HealthCheckType = aws.String(d.Get("health_check_type").(string)) 503 } 504 505 if d.HasChange("vpc_zone_identifier") { 506 opts.VPCZoneIdentifier = expandVpcZoneIdentifiers(d.Get("vpc_zone_identifier").(*schema.Set).List()) 507 } 508 509 if d.HasChange("availability_zones") { 510 if v, ok := d.GetOk("availability_zones"); ok && v.(*schema.Set).Len() > 0 { 511 opts.AvailabilityZones = expandStringList(v.(*schema.Set).List()) 512 } 513 } 514 515 if d.HasChange("placement_group") { 516 opts.PlacementGroup = aws.String(d.Get("placement_group").(string)) 517 } 518 519 if d.HasChange("termination_policies") { 520 // If the termination policy is set to null, we need to explicitly set 521 // it back to "Default", or the API won't reset it for us. 522 if v, ok := d.GetOk("termination_policies"); ok && len(v.([]interface{})) > 0 { 523 opts.TerminationPolicies = expandStringList(v.([]interface{})) 524 } else { 525 log.Printf("[DEBUG] Explicitly setting null termination policy to 'Default'") 526 opts.TerminationPolicies = aws.StringSlice([]string{"Default"}) 527 } 528 } 529 530 if err := setAutoscalingTags(conn, d); err != nil { 531 return err 532 } else { 533 d.SetPartial("tag") 534 } 535 536 log.Printf("[DEBUG] AutoScaling Group update configuration: %#v", opts) 537 _, err := conn.UpdateAutoScalingGroup(&opts) 538 if err != nil { 539 d.Partial(true) 540 return fmt.Errorf("Error updating Autoscaling group: %s", err) 541 } 542 543 if d.HasChange("load_balancers") { 544 545 o, n := d.GetChange("load_balancers") 546 if o == nil { 547 o = new(schema.Set) 548 } 549 if n == nil { 550 n = new(schema.Set) 551 } 552 553 os := o.(*schema.Set) 554 ns := n.(*schema.Set) 555 remove := expandStringList(os.Difference(ns).List()) 556 add := expandStringList(ns.Difference(os).List()) 557 558 if len(remove) > 0 { 559 _, err := conn.DetachLoadBalancers(&autoscaling.DetachLoadBalancersInput{ 560 AutoScalingGroupName: aws.String(d.Id()), 561 LoadBalancerNames: remove, 562 }) 563 if err != nil { 564 return fmt.Errorf("[WARN] Error updating Load Balancers for AutoScaling Group (%s), error: %s", d.Id(), err) 565 } 566 } 567 568 if len(add) > 0 { 569 _, err := conn.AttachLoadBalancers(&autoscaling.AttachLoadBalancersInput{ 570 AutoScalingGroupName: aws.String(d.Id()), 571 LoadBalancerNames: add, 572 }) 573 if err != nil { 574 return fmt.Errorf("[WARN] Error updating Load Balancers for AutoScaling Group (%s), error: %s", d.Id(), err) 575 } 576 } 577 } 578 579 if d.HasChange("target_group_arns") { 580 581 o, n := d.GetChange("target_group_arns") 582 if o == nil { 583 o = new(schema.Set) 584 } 585 if n == nil { 586 n = new(schema.Set) 587 } 588 589 os := o.(*schema.Set) 590 ns := n.(*schema.Set) 591 remove := expandStringList(os.Difference(ns).List()) 592 add := expandStringList(ns.Difference(os).List()) 593 594 if len(remove) > 0 { 595 _, err := conn.DetachLoadBalancerTargetGroups(&autoscaling.DetachLoadBalancerTargetGroupsInput{ 596 AutoScalingGroupName: aws.String(d.Id()), 597 TargetGroupARNs: remove, 598 }) 599 if err != nil { 600 return fmt.Errorf("[WARN] Error updating Load Balancers Target Groups for AutoScaling Group (%s), error: %s", d.Id(), err) 601 } 602 } 603 604 if len(add) > 0 { 605 _, err := conn.AttachLoadBalancerTargetGroups(&autoscaling.AttachLoadBalancerTargetGroupsInput{ 606 AutoScalingGroupName: aws.String(d.Id()), 607 TargetGroupARNs: add, 608 }) 609 if err != nil { 610 return fmt.Errorf("[WARN] Error updating Load Balancers Target Groups for AutoScaling Group (%s), error: %s", d.Id(), err) 611 } 612 } 613 } 614 615 if shouldWaitForCapacity { 616 if err := waitForASGCapacity(d, meta, capacitySatisfiedUpdate); err != nil { 617 return errwrap.Wrapf("Error waiting for AutoScaling Group Capacity: {{err}}", err) 618 } 619 } 620 621 if d.HasChange("enabled_metrics") { 622 if err := updateASGMetricsCollection(d, conn); err != nil { 623 return errwrap.Wrapf("Error updating AutoScaling Group Metrics collection: {{err}}", err) 624 } 625 } 626 627 if d.HasChange("suspended_processes") { 628 if err := updateASGSuspendedProcesses(d, conn); err != nil { 629 return errwrap.Wrapf("Error updating AutoScaling Group Suspended Processes: {{err}}", err) 630 } 631 } 632 633 return resourceAwsAutoscalingGroupRead(d, meta) 634 } 635 636 func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) error { 637 conn := meta.(*AWSClient).autoscalingconn 638 639 // Read the autoscaling group first. If it doesn't exist, we're done. 640 // We need the group in order to check if there are instances attached. 641 // If so, we need to remove those first. 642 g, err := getAwsAutoscalingGroup(d.Id(), conn) 643 if err != nil { 644 return err 645 } 646 if g == nil { 647 log.Printf("[INFO] Autoscaling Group %q not found", d.Id()) 648 d.SetId("") 649 return nil 650 } 651 if len(g.Instances) > 0 || *g.DesiredCapacity > 0 { 652 if err := resourceAwsAutoscalingGroupDrain(d, meta); err != nil { 653 return err 654 } 655 } 656 657 log.Printf("[DEBUG] AutoScaling Group destroy: %v", d.Id()) 658 deleteopts := autoscaling.DeleteAutoScalingGroupInput{ 659 AutoScalingGroupName: aws.String(d.Id()), 660 ForceDelete: aws.Bool(d.Get("force_delete").(bool)), 661 } 662 663 // We retry the delete operation to handle InUse/InProgress errors coming 664 // from scaling operations. We should be able to sneak in a delete in between 665 // scaling operations within 5m. 666 err = resource.Retry(5*time.Minute, func() *resource.RetryError { 667 if _, err := conn.DeleteAutoScalingGroup(&deleteopts); err != nil { 668 if awserr, ok := err.(awserr.Error); ok { 669 switch awserr.Code() { 670 case "InvalidGroup.NotFound": 671 // Already gone? Sure! 672 return nil 673 case "ResourceInUse", "ScalingActivityInProgress": 674 // These are retryable 675 return resource.RetryableError(awserr) 676 } 677 } 678 // Didn't recognize the error, so shouldn't retry. 679 return resource.NonRetryableError(err) 680 } 681 // Successful delete 682 return nil 683 }) 684 if err != nil { 685 return err 686 } 687 688 return resource.Retry(5*time.Minute, func() *resource.RetryError { 689 if g, _ = getAwsAutoscalingGroup(d.Id(), conn); g != nil { 690 return resource.RetryableError( 691 fmt.Errorf("Auto Scaling Group still exists")) 692 } 693 return nil 694 }) 695 } 696 697 func getAwsAutoscalingGroup( 698 asgName string, 699 conn *autoscaling.AutoScaling) (*autoscaling.Group, error) { 700 701 describeOpts := autoscaling.DescribeAutoScalingGroupsInput{ 702 AutoScalingGroupNames: []*string{aws.String(asgName)}, 703 } 704 705 log.Printf("[DEBUG] AutoScaling Group describe configuration: %#v", describeOpts) 706 describeGroups, err := conn.DescribeAutoScalingGroups(&describeOpts) 707 if err != nil { 708 autoscalingerr, ok := err.(awserr.Error) 709 if ok && autoscalingerr.Code() == "InvalidGroup.NotFound" { 710 return nil, nil 711 } 712 713 return nil, fmt.Errorf("Error retrieving AutoScaling groups: %s", err) 714 } 715 716 // Search for the autoscaling group 717 for idx, asc := range describeGroups.AutoScalingGroups { 718 if *asc.AutoScalingGroupName == asgName { 719 return describeGroups.AutoScalingGroups[idx], nil 720 } 721 } 722 723 return nil, nil 724 } 725 726 func resourceAwsAutoscalingGroupDrain(d *schema.ResourceData, meta interface{}) error { 727 conn := meta.(*AWSClient).autoscalingconn 728 729 if d.Get("force_delete").(bool) { 730 log.Printf("[DEBUG] Skipping ASG drain, force_delete was set.") 731 return nil 732 } 733 734 // First, set the capacity to zero so the group will drain 735 log.Printf("[DEBUG] Reducing autoscaling group capacity to zero") 736 opts := autoscaling.UpdateAutoScalingGroupInput{ 737 AutoScalingGroupName: aws.String(d.Id()), 738 DesiredCapacity: aws.Int64(0), 739 MinSize: aws.Int64(0), 740 MaxSize: aws.Int64(0), 741 } 742 if _, err := conn.UpdateAutoScalingGroup(&opts); err != nil { 743 return fmt.Errorf("Error setting capacity to zero to drain: %s", err) 744 } 745 746 // Next, wait for the autoscale group to drain 747 log.Printf("[DEBUG] Waiting for group to have zero instances") 748 return resource.Retry(10*time.Minute, func() *resource.RetryError { 749 g, err := getAwsAutoscalingGroup(d.Id(), conn) 750 if err != nil { 751 return resource.NonRetryableError(err) 752 } 753 if g == nil { 754 log.Printf("[INFO] Autoscaling Group %q not found", d.Id()) 755 d.SetId("") 756 return nil 757 } 758 759 if len(g.Instances) == 0 { 760 return nil 761 } 762 763 return resource.RetryableError( 764 fmt.Errorf("group still has %d instances", len(g.Instances))) 765 }) 766 } 767 768 func enableASGSuspendedProcesses(d *schema.ResourceData, conn *autoscaling.AutoScaling) error { 769 props := &autoscaling.ScalingProcessQuery{ 770 AutoScalingGroupName: aws.String(d.Id()), 771 ScalingProcesses: expandStringList(d.Get("suspended_processes").(*schema.Set).List()), 772 } 773 774 _, err := conn.SuspendProcesses(props) 775 if err != nil { 776 return err 777 } 778 779 return nil 780 } 781 782 func enableASGMetricsCollection(d *schema.ResourceData, conn *autoscaling.AutoScaling) error { 783 props := &autoscaling.EnableMetricsCollectionInput{ 784 AutoScalingGroupName: aws.String(d.Id()), 785 Granularity: aws.String(d.Get("metrics_granularity").(string)), 786 Metrics: expandStringList(d.Get("enabled_metrics").(*schema.Set).List()), 787 } 788 789 log.Printf("[INFO] Enabling metrics collection for the ASG: %s", d.Id()) 790 _, metricsErr := conn.EnableMetricsCollection(props) 791 if metricsErr != nil { 792 return metricsErr 793 } 794 795 return nil 796 } 797 798 func updateASGSuspendedProcesses(d *schema.ResourceData, conn *autoscaling.AutoScaling) error { 799 o, n := d.GetChange("suspended_processes") 800 if o == nil { 801 o = new(schema.Set) 802 } 803 if n == nil { 804 n = new(schema.Set) 805 } 806 807 os := o.(*schema.Set) 808 ns := n.(*schema.Set) 809 810 resumeProcesses := os.Difference(ns) 811 if resumeProcesses.Len() != 0 { 812 props := &autoscaling.ScalingProcessQuery{ 813 AutoScalingGroupName: aws.String(d.Id()), 814 ScalingProcesses: expandStringList(resumeProcesses.List()), 815 } 816 817 _, err := conn.ResumeProcesses(props) 818 if err != nil { 819 return fmt.Errorf("Error Resuming Processes for ASG %q: %s", d.Id(), err) 820 } 821 } 822 823 suspendedProcesses := ns.Difference(os) 824 if suspendedProcesses.Len() != 0 { 825 props := &autoscaling.ScalingProcessQuery{ 826 AutoScalingGroupName: aws.String(d.Id()), 827 ScalingProcesses: expandStringList(suspendedProcesses.List()), 828 } 829 830 _, err := conn.SuspendProcesses(props) 831 if err != nil { 832 return fmt.Errorf("Error Suspending Processes for ASG %q: %s", d.Id(), err) 833 } 834 } 835 836 return nil 837 838 } 839 840 func updateASGMetricsCollection(d *schema.ResourceData, conn *autoscaling.AutoScaling) error { 841 842 o, n := d.GetChange("enabled_metrics") 843 if o == nil { 844 o = new(schema.Set) 845 } 846 if n == nil { 847 n = new(schema.Set) 848 } 849 850 os := o.(*schema.Set) 851 ns := n.(*schema.Set) 852 853 disableMetrics := os.Difference(ns) 854 if disableMetrics.Len() != 0 { 855 props := &autoscaling.DisableMetricsCollectionInput{ 856 AutoScalingGroupName: aws.String(d.Id()), 857 Metrics: expandStringList(disableMetrics.List()), 858 } 859 860 _, err := conn.DisableMetricsCollection(props) 861 if err != nil { 862 return fmt.Errorf("Failure to Disable metrics collection types for ASG %s: %s", d.Id(), err) 863 } 864 } 865 866 enabledMetrics := ns.Difference(os) 867 if enabledMetrics.Len() != 0 { 868 props := &autoscaling.EnableMetricsCollectionInput{ 869 AutoScalingGroupName: aws.String(d.Id()), 870 Metrics: expandStringList(enabledMetrics.List()), 871 Granularity: aws.String(d.Get("metrics_granularity").(string)), 872 } 873 874 _, err := conn.EnableMetricsCollection(props) 875 if err != nil { 876 return fmt.Errorf("Failure to Enable metrics collection types for ASG %s: %s", d.Id(), err) 877 } 878 } 879 880 return nil 881 } 882 883 // getELBInstanceStates returns a mapping of the instance states of all the ELBs attached to the 884 // provided ASG. 885 // 886 // Note that this is the instance state function for ELB Classic. 887 // 888 // Nested like: lbName -> instanceId -> instanceState 889 func getELBInstanceStates(g *autoscaling.Group, meta interface{}) (map[string]map[string]string, error) { 890 lbInstanceStates := make(map[string]map[string]string) 891 elbconn := meta.(*AWSClient).elbconn 892 893 for _, lbName := range g.LoadBalancerNames { 894 lbInstanceStates[*lbName] = make(map[string]string) 895 opts := &elb.DescribeInstanceHealthInput{LoadBalancerName: lbName} 896 r, err := elbconn.DescribeInstanceHealth(opts) 897 if err != nil { 898 return nil, err 899 } 900 for _, is := range r.InstanceStates { 901 if is.InstanceId == nil || is.State == nil { 902 continue 903 } 904 lbInstanceStates[*lbName][*is.InstanceId] = *is.State 905 } 906 } 907 908 return lbInstanceStates, nil 909 } 910 911 // getTargetGroupInstanceStates returns a mapping of the instance states of 912 // all the ALB target groups attached to the provided ASG. 913 // 914 // Note that this is the instance state function for Application Load 915 // Balancing (aka ELBv2). 916 // 917 // Nested like: targetGroupARN -> instanceId -> instanceState 918 func getTargetGroupInstanceStates(g *autoscaling.Group, meta interface{}) (map[string]map[string]string, error) { 919 targetInstanceStates := make(map[string]map[string]string) 920 elbv2conn := meta.(*AWSClient).elbv2conn 921 922 for _, targetGroupARN := range g.TargetGroupARNs { 923 targetInstanceStates[*targetGroupARN] = make(map[string]string) 924 opts := &elbv2.DescribeTargetHealthInput{TargetGroupArn: targetGroupARN} 925 r, err := elbv2conn.DescribeTargetHealth(opts) 926 if err != nil { 927 return nil, err 928 } 929 for _, desc := range r.TargetHealthDescriptions { 930 if desc.Target == nil || desc.Target.Id == nil || desc.TargetHealth == nil || desc.TargetHealth.State == nil { 931 continue 932 } 933 targetInstanceStates[*targetGroupARN][*desc.Target.Id] = *desc.TargetHealth.State 934 } 935 } 936 937 return targetInstanceStates, nil 938 } 939 940 func expandVpcZoneIdentifiers(list []interface{}) *string { 941 strs := make([]string, len(list)) 942 for _, s := range list { 943 strs = append(strs, s.(string)) 944 } 945 return aws.String(strings.Join(strs, ",")) 946 }