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