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