github.com/markdia/terraform@v0.5.1-0.20150508012022-f1ae920aa970/builtin/providers/aws/resource_aws_autoscaling_group.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 "time" 8 9 "github.com/hashicorp/terraform/helper/resource" 10 "github.com/hashicorp/terraform/helper/schema" 11 12 "github.com/awslabs/aws-sdk-go/aws" 13 "github.com/awslabs/aws-sdk-go/service/autoscaling" 14 ) 15 16 func resourceAwsAutoscalingGroup() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceAwsAutoscalingGroupCreate, 19 Read: resourceAwsAutoscalingGroupRead, 20 Update: resourceAwsAutoscalingGroupUpdate, 21 Delete: resourceAwsAutoscalingGroupDelete, 22 23 Schema: map[string]*schema.Schema{ 24 "name": &schema.Schema{ 25 Type: schema.TypeString, 26 Required: true, 27 ForceNew: true, 28 }, 29 30 "launch_configuration": &schema.Schema{ 31 Type: schema.TypeString, 32 Required: true, 33 }, 34 35 "desired_capacity": &schema.Schema{ 36 Type: schema.TypeInt, 37 Optional: true, 38 Computed: true, 39 }, 40 41 "min_size": &schema.Schema{ 42 Type: schema.TypeInt, 43 Required: true, 44 }, 45 46 "max_size": &schema.Schema{ 47 Type: schema.TypeInt, 48 Required: true, 49 }, 50 51 "default_cooldown": &schema.Schema{ 52 Type: schema.TypeInt, 53 Optional: true, 54 Computed: true, 55 ForceNew: true, 56 }, 57 58 "force_delete": &schema.Schema{ 59 Type: schema.TypeBool, 60 Optional: true, 61 Computed: true, 62 ForceNew: true, 63 }, 64 65 "health_check_grace_period": &schema.Schema{ 66 Type: schema.TypeInt, 67 Optional: true, 68 Computed: true, 69 }, 70 71 "health_check_type": &schema.Schema{ 72 Type: schema.TypeString, 73 Optional: true, 74 Computed: true, 75 ForceNew: true, 76 }, 77 78 "availability_zones": &schema.Schema{ 79 Type: schema.TypeSet, 80 Required: true, 81 ForceNew: true, 82 Elem: &schema.Schema{Type: schema.TypeString}, 83 Set: schema.HashString, 84 }, 85 86 "load_balancers": &schema.Schema{ 87 Type: schema.TypeSet, 88 Optional: true, 89 ForceNew: true, 90 Elem: &schema.Schema{Type: schema.TypeString}, 91 Set: schema.HashString, 92 }, 93 94 "vpc_zone_identifier": &schema.Schema{ 95 Type: schema.TypeSet, 96 Optional: true, 97 Computed: true, 98 ForceNew: true, 99 Elem: &schema.Schema{Type: schema.TypeString}, 100 Set: schema.HashString, 101 }, 102 103 "termination_policies": &schema.Schema{ 104 Type: schema.TypeSet, 105 Optional: true, 106 Computed: true, 107 ForceNew: true, 108 Elem: &schema.Schema{Type: schema.TypeString}, 109 Set: schema.HashString, 110 }, 111 112 "tag": autoscalingTagsSchema(), 113 }, 114 } 115 } 116 117 func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) error { 118 conn := meta.(*AWSClient).autoscalingconn 119 120 var autoScalingGroupOpts autoscaling.CreateAutoScalingGroupInput 121 autoScalingGroupOpts.AutoScalingGroupName = aws.String(d.Get("name").(string)) 122 autoScalingGroupOpts.LaunchConfigurationName = aws.String(d.Get("launch_configuration").(string)) 123 autoScalingGroupOpts.MinSize = aws.Long(int64(d.Get("min_size").(int))) 124 autoScalingGroupOpts.MaxSize = aws.Long(int64(d.Get("max_size").(int))) 125 autoScalingGroupOpts.AvailabilityZones = expandStringList( 126 d.Get("availability_zones").(*schema.Set).List()) 127 128 if v, ok := d.GetOk("tag"); ok { 129 autoScalingGroupOpts.Tags = autoscalingTagsFromMap( 130 setToMapByKey(v.(*schema.Set), "key"), d.Get("name").(string)) 131 } 132 133 if v, ok := d.GetOk("default_cooldown"); ok { 134 autoScalingGroupOpts.DefaultCooldown = aws.Long(int64(v.(int))) 135 } 136 137 if v, ok := d.GetOk("health_check_type"); ok && v.(string) != "" { 138 autoScalingGroupOpts.HealthCheckType = aws.String(v.(string)) 139 } 140 141 if v, ok := d.GetOk("desired_capacity"); ok { 142 autoScalingGroupOpts.DesiredCapacity = aws.Long(int64(v.(int))) 143 } 144 145 if v, ok := d.GetOk("health_check_grace_period"); ok { 146 autoScalingGroupOpts.HealthCheckGracePeriod = aws.Long(int64(v.(int))) 147 } 148 149 if v, ok := d.GetOk("load_balancers"); ok && v.(*schema.Set).Len() > 0 { 150 autoScalingGroupOpts.LoadBalancerNames = expandStringList( 151 v.(*schema.Set).List()) 152 } 153 154 if v, ok := d.GetOk("vpc_zone_identifier"); ok && v.(*schema.Set).Len() > 0 { 155 exp := expandStringList(v.(*schema.Set).List()) 156 strs := make([]string, len(exp)) 157 for _, s := range exp { 158 strs = append(strs, *s) 159 } 160 autoScalingGroupOpts.VPCZoneIdentifier = aws.String(strings.Join(strs, ",")) 161 } 162 163 if v, ok := d.GetOk("termination_policies"); ok && v.(*schema.Set).Len() > 0 { 164 autoScalingGroupOpts.TerminationPolicies = expandStringList( 165 v.(*schema.Set).List()) 166 } 167 168 log.Printf("[DEBUG] AutoScaling Group create configuration: %#v", autoScalingGroupOpts) 169 _, err := conn.CreateAutoScalingGroup(&autoScalingGroupOpts) 170 if err != nil { 171 return fmt.Errorf("Error creating Autoscaling Group: %s", err) 172 } 173 174 d.SetId(d.Get("name").(string)) 175 log.Printf("[INFO] AutoScaling Group ID: %s", d.Id()) 176 177 if err := waitForASGCapacity(d, meta); err != nil { 178 return err 179 } 180 181 return resourceAwsAutoscalingGroupRead(d, meta) 182 } 183 184 func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) error { 185 g, err := getAwsAutoscalingGroup(d, meta) 186 if err != nil { 187 return err 188 } 189 if g == nil { 190 return nil 191 } 192 193 d.Set("availability_zones", g.AvailabilityZones) 194 d.Set("default_cooldown", g.DefaultCooldown) 195 d.Set("desired_capacity", g.DesiredCapacity) 196 d.Set("health_check_grace_period", g.HealthCheckGracePeriod) 197 d.Set("health_check_type", g.HealthCheckType) 198 d.Set("launch_configuration", g.LaunchConfigurationName) 199 d.Set("load_balancers", g.LoadBalancerNames) 200 d.Set("min_size", g.MinSize) 201 d.Set("max_size", g.MaxSize) 202 d.Set("name", g.AutoScalingGroupName) 203 d.Set("tag", g.Tags) 204 d.Set("vpc_zone_identifier", strings.Split(*g.VPCZoneIdentifier, ",")) 205 d.Set("termination_policies", g.TerminationPolicies) 206 207 return nil 208 } 209 210 func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) error { 211 conn := meta.(*AWSClient).autoscalingconn 212 213 opts := autoscaling.UpdateAutoScalingGroupInput{ 214 AutoScalingGroupName: aws.String(d.Id()), 215 } 216 217 if d.HasChange("desired_capacity") { 218 opts.DesiredCapacity = aws.Long(int64(d.Get("desired_capacity").(int))) 219 } 220 221 if d.HasChange("launch_configuration") { 222 opts.LaunchConfigurationName = aws.String(d.Get("launch_configuration").(string)) 223 } 224 225 if d.HasChange("min_size") { 226 opts.MinSize = aws.Long(int64(d.Get("min_size").(int))) 227 } 228 229 if d.HasChange("max_size") { 230 opts.MaxSize = aws.Long(int64(d.Get("max_size").(int))) 231 } 232 233 if d.HasChange("health_check_grace_period") { 234 opts.HealthCheckGracePeriod = aws.Long(int64(d.Get("health_check_grace_period").(int))) 235 } 236 237 if err := setAutoscalingTags(conn, d); err != nil { 238 return err 239 } else { 240 d.SetPartial("tag") 241 } 242 243 log.Printf("[DEBUG] AutoScaling Group update configuration: %#v", opts) 244 _, err := conn.UpdateAutoScalingGroup(&opts) 245 if err != nil { 246 d.Partial(true) 247 return fmt.Errorf("Error updating Autoscaling group: %s", err) 248 } 249 250 return resourceAwsAutoscalingGroupRead(d, meta) 251 } 252 253 func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) error { 254 conn := meta.(*AWSClient).autoscalingconn 255 256 // Read the autoscaling group first. If it doesn't exist, we're done. 257 // We need the group in order to check if there are instances attached. 258 // If so, we need to remove those first. 259 g, err := getAwsAutoscalingGroup(d, meta) 260 if err != nil { 261 return err 262 } 263 if g == nil { 264 return nil 265 } 266 if len(g.Instances) > 0 || *g.DesiredCapacity > 0 { 267 if err := resourceAwsAutoscalingGroupDrain(d, meta); err != nil { 268 return err 269 } 270 } 271 272 log.Printf("[DEBUG] AutoScaling Group destroy: %v", d.Id()) 273 deleteopts := autoscaling.DeleteAutoScalingGroupInput{AutoScalingGroupName: aws.String(d.Id())} 274 275 // You can force an autoscaling group to delete 276 // even if it's in the process of scaling a resource. 277 // Normally, you would set the min-size and max-size to 0,0 278 // and then delete the group. This bypasses that and leaves 279 // resources potentially dangling. 280 if d.Get("force_delete").(bool) { 281 deleteopts.ForceDelete = aws.Boolean(true) 282 } 283 284 // We retry the delete operation to handle InUse/InProgress errors coming 285 // from scaling operations. We should be able to sneak in a delete in between 286 // scaling operations within 5m. 287 err = resource.Retry(5*time.Minute, func() error { 288 if _, err := conn.DeleteAutoScalingGroup(&deleteopts); err != nil { 289 if awserr, ok := err.(aws.APIError); ok { 290 switch awserr.Code { 291 case "InvalidGroup.NotFound": 292 // Already gone? Sure! 293 return nil 294 case "ResourceInUse", "ScalingActivityInProgress": 295 // These are retryable 296 return awserr 297 } 298 } 299 // Didn't recognize the error, so shouldn't retry. 300 return resource.RetryError{Err: err} 301 } 302 // Successful delete 303 return nil 304 }) 305 if err != nil { 306 return err 307 } 308 309 return resource.Retry(5*time.Minute, func() error { 310 if g, _ = getAwsAutoscalingGroup(d, meta); g != nil { 311 return fmt.Errorf("Auto Scaling Group still exists") 312 } 313 return nil 314 }) 315 } 316 317 func getAwsAutoscalingGroup( 318 d *schema.ResourceData, 319 meta interface{}) (*autoscaling.AutoScalingGroup, error) { 320 conn := meta.(*AWSClient).autoscalingconn 321 322 describeOpts := autoscaling.DescribeAutoScalingGroupsInput{ 323 AutoScalingGroupNames: []*string{aws.String(d.Id())}, 324 } 325 326 log.Printf("[DEBUG] AutoScaling Group describe configuration: %#v", describeOpts) 327 describeGroups, err := conn.DescribeAutoScalingGroups(&describeOpts) 328 if err != nil { 329 autoscalingerr, ok := err.(aws.APIError) 330 if ok && autoscalingerr.Code == "InvalidGroup.NotFound" { 331 d.SetId("") 332 return nil, nil 333 } 334 335 return nil, fmt.Errorf("Error retrieving AutoScaling groups: %s", err) 336 } 337 338 // Search for the autoscaling group 339 for idx, asc := range describeGroups.AutoScalingGroups { 340 if *asc.AutoScalingGroupName == d.Id() { 341 return describeGroups.AutoScalingGroups[idx], nil 342 } 343 } 344 345 // ASG not found 346 d.SetId("") 347 return nil, nil 348 } 349 350 func resourceAwsAutoscalingGroupDrain(d *schema.ResourceData, meta interface{}) error { 351 conn := meta.(*AWSClient).autoscalingconn 352 353 // First, set the capacity to zero so the group will drain 354 log.Printf("[DEBUG] Reducing autoscaling group capacity to zero") 355 opts := autoscaling.UpdateAutoScalingGroupInput{ 356 AutoScalingGroupName: aws.String(d.Id()), 357 DesiredCapacity: aws.Long(0), 358 MinSize: aws.Long(0), 359 MaxSize: aws.Long(0), 360 } 361 if _, err := conn.UpdateAutoScalingGroup(&opts); err != nil { 362 return fmt.Errorf("Error setting capacity to zero to drain: %s", err) 363 } 364 365 // Next, wait for the autoscale group to drain 366 log.Printf("[DEBUG] Waiting for group to have zero instances") 367 return resource.Retry(10*time.Minute, func() error { 368 g, err := getAwsAutoscalingGroup(d, meta) 369 if err != nil { 370 return resource.RetryError{Err: err} 371 } 372 if g == nil { 373 return nil 374 } 375 376 if len(g.Instances) == 0 { 377 return nil 378 } 379 380 return fmt.Errorf("group still has %d instances", len(g.Instances)) 381 }) 382 } 383 384 var waitForASGCapacityTimeout = 10 * time.Minute 385 386 // Waits for a minimum number of healthy instances to show up as healthy in the 387 // ASG before continuing. Waits up to `waitForASGCapacityTimeout` for 388 // "desired_capacity", or "min_size" if desired capacity is not specified. 389 func waitForASGCapacity(d *schema.ResourceData, meta interface{}) error { 390 waitFor := d.Get("min_size").(int) 391 if v := d.Get("desired_capacity").(int); v > 0 { 392 waitFor = v 393 } 394 395 log.Printf("[DEBUG] Waiting for group to have %d healthy instances", waitFor) 396 return resource.Retry(waitForASGCapacityTimeout, func() error { 397 g, err := getAwsAutoscalingGroup(d, meta) 398 if err != nil { 399 return resource.RetryError{Err: err} 400 } 401 if g == nil { 402 return nil 403 } 404 405 healthy := 0 406 for _, i := range g.Instances { 407 if i.HealthStatus == nil { 408 continue 409 } 410 if strings.EqualFold(*i.HealthStatus, "Healthy") { 411 healthy++ 412 } 413 } 414 415 log.Printf( 416 "[DEBUG] %q has %d/%d healthy instances", d.Id(), healthy, waitFor) 417 418 if healthy >= waitFor { 419 return nil 420 } 421 422 return fmt.Errorf("Waiting for healthy instances: %d/%d", healthy, waitFor) 423 }) 424 }