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