github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/builtin/providers/aws/resource_aws_launch_configuration.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "crypto/sha1" 6 "encoding/base64" 7 "encoding/hex" 8 "fmt" 9 "log" 10 "time" 11 12 "github.com/aws/aws-sdk-go/aws" 13 "github.com/aws/aws-sdk-go/aws/awserr" 14 "github.com/aws/aws-sdk-go/service/autoscaling" 15 "github.com/aws/aws-sdk-go/service/ec2" 16 "github.com/hashicorp/terraform/helper/hashcode" 17 "github.com/hashicorp/terraform/helper/resource" 18 "github.com/hashicorp/terraform/helper/schema" 19 ) 20 21 func resourceAwsLaunchConfiguration() *schema.Resource { 22 return &schema.Resource{ 23 Create: resourceAwsLaunchConfigurationCreate, 24 Read: resourceAwsLaunchConfigurationRead, 25 Delete: resourceAwsLaunchConfigurationDelete, 26 27 Schema: map[string]*schema.Schema{ 28 "name": &schema.Schema{ 29 Type: schema.TypeString, 30 Optional: true, 31 Computed: true, 32 ForceNew: true, 33 ConflictsWith: []string{"name_prefix"}, 34 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 35 // https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1932-L1939 36 value := v.(string) 37 if len(value) > 255 { 38 errors = append(errors, fmt.Errorf( 39 "%q cannot be longer than 255 characters", k)) 40 } 41 return 42 }, 43 }, 44 45 "name_prefix": &schema.Schema{ 46 Type: schema.TypeString, 47 Optional: true, 48 ForceNew: true, 49 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 50 // https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1932-L1939 51 // uuid is 26 characters, limit the prefix to 229. 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 "image_id": &schema.Schema{ 62 Type: schema.TypeString, 63 Required: true, 64 ForceNew: true, 65 }, 66 67 "instance_type": &schema.Schema{ 68 Type: schema.TypeString, 69 Required: true, 70 ForceNew: true, 71 }, 72 73 "iam_instance_profile": &schema.Schema{ 74 Type: schema.TypeString, 75 Optional: true, 76 ForceNew: true, 77 }, 78 79 "key_name": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 Computed: true, 83 ForceNew: true, 84 }, 85 86 "user_data": &schema.Schema{ 87 Type: schema.TypeString, 88 Optional: true, 89 ForceNew: true, 90 StateFunc: func(v interface{}) string { 91 switch v.(type) { 92 case string: 93 hash := sha1.Sum([]byte(v.(string))) 94 return hex.EncodeToString(hash[:]) 95 default: 96 return "" 97 } 98 }, 99 }, 100 101 "security_groups": &schema.Schema{ 102 Type: schema.TypeSet, 103 Optional: true, 104 ForceNew: true, 105 Elem: &schema.Schema{Type: schema.TypeString}, 106 Set: schema.HashString, 107 }, 108 109 "associate_public_ip_address": &schema.Schema{ 110 Type: schema.TypeBool, 111 Optional: true, 112 ForceNew: true, 113 Default: false, 114 }, 115 116 "spot_price": &schema.Schema{ 117 Type: schema.TypeString, 118 Optional: true, 119 ForceNew: true, 120 }, 121 122 "ebs_optimized": &schema.Schema{ 123 Type: schema.TypeBool, 124 Optional: true, 125 ForceNew: true, 126 Computed: true, 127 }, 128 129 "placement_tenancy": &schema.Schema{ 130 Type: schema.TypeString, 131 Optional: true, 132 ForceNew: true, 133 }, 134 135 "enable_monitoring": &schema.Schema{ 136 Type: schema.TypeBool, 137 Optional: true, 138 ForceNew: true, 139 Default: true, 140 }, 141 142 "ebs_block_device": &schema.Schema{ 143 Type: schema.TypeSet, 144 Optional: true, 145 Computed: true, 146 Elem: &schema.Resource{ 147 Schema: map[string]*schema.Schema{ 148 "delete_on_termination": &schema.Schema{ 149 Type: schema.TypeBool, 150 Optional: true, 151 Default: true, 152 ForceNew: true, 153 }, 154 155 "device_name": &schema.Schema{ 156 Type: schema.TypeString, 157 Required: true, 158 ForceNew: true, 159 }, 160 161 "iops": &schema.Schema{ 162 Type: schema.TypeInt, 163 Optional: true, 164 Computed: true, 165 ForceNew: true, 166 }, 167 168 "snapshot_id": &schema.Schema{ 169 Type: schema.TypeString, 170 Optional: true, 171 Computed: true, 172 ForceNew: true, 173 }, 174 175 "volume_size": &schema.Schema{ 176 Type: schema.TypeInt, 177 Optional: true, 178 Computed: true, 179 ForceNew: true, 180 }, 181 182 "volume_type": &schema.Schema{ 183 Type: schema.TypeString, 184 Optional: true, 185 Computed: true, 186 ForceNew: true, 187 }, 188 }, 189 }, 190 Set: func(v interface{}) int { 191 var buf bytes.Buffer 192 m := v.(map[string]interface{}) 193 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 194 buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string))) 195 return hashcode.String(buf.String()) 196 }, 197 }, 198 199 "ephemeral_block_device": &schema.Schema{ 200 Type: schema.TypeSet, 201 Optional: true, 202 ForceNew: true, 203 Elem: &schema.Resource{ 204 Schema: map[string]*schema.Schema{ 205 "device_name": &schema.Schema{ 206 Type: schema.TypeString, 207 Required: true, 208 }, 209 210 "virtual_name": &schema.Schema{ 211 Type: schema.TypeString, 212 Required: true, 213 }, 214 }, 215 }, 216 Set: func(v interface{}) int { 217 var buf bytes.Buffer 218 m := v.(map[string]interface{}) 219 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 220 buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string))) 221 return hashcode.String(buf.String()) 222 }, 223 }, 224 225 "root_block_device": &schema.Schema{ 226 // TODO: This is a set because we don't support singleton 227 // sub-resources today. We'll enforce that the set only ever has 228 // length zero or one below. When TF gains support for 229 // sub-resources this can be converted. 230 Type: schema.TypeSet, 231 Optional: true, 232 Computed: true, 233 Elem: &schema.Resource{ 234 // "You can only modify the volume size, volume type, and Delete on 235 // Termination flag on the block device mapping entry for the root 236 // device volume." - bit.ly/ec2bdmap 237 Schema: map[string]*schema.Schema{ 238 "delete_on_termination": &schema.Schema{ 239 Type: schema.TypeBool, 240 Optional: true, 241 Default: true, 242 ForceNew: true, 243 }, 244 245 "iops": &schema.Schema{ 246 Type: schema.TypeInt, 247 Optional: true, 248 Computed: true, 249 ForceNew: true, 250 }, 251 252 "volume_size": &schema.Schema{ 253 Type: schema.TypeInt, 254 Optional: true, 255 Computed: true, 256 ForceNew: true, 257 }, 258 259 "volume_type": &schema.Schema{ 260 Type: schema.TypeString, 261 Optional: true, 262 Computed: true, 263 ForceNew: true, 264 }, 265 }, 266 }, 267 Set: func(v interface{}) int { 268 // there can be only one root device; no need to hash anything 269 return 0 270 }, 271 }, 272 }, 273 } 274 } 275 276 func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface{}) error { 277 autoscalingconn := meta.(*AWSClient).autoscalingconn 278 ec2conn := meta.(*AWSClient).ec2conn 279 280 createLaunchConfigurationOpts := autoscaling.CreateLaunchConfigurationInput{ 281 LaunchConfigurationName: aws.String(d.Get("name").(string)), 282 ImageId: aws.String(d.Get("image_id").(string)), 283 InstanceType: aws.String(d.Get("instance_type").(string)), 284 EbsOptimized: aws.Bool(d.Get("ebs_optimized").(bool)), 285 } 286 287 if v, ok := d.GetOk("user_data"); ok { 288 userData := base64.StdEncoding.EncodeToString([]byte(v.(string))) 289 createLaunchConfigurationOpts.UserData = aws.String(userData) 290 } 291 292 createLaunchConfigurationOpts.InstanceMonitoring = &autoscaling.InstanceMonitoring{ 293 Enabled: aws.Bool(d.Get("enable_monitoring").(bool)), 294 } 295 296 if v, ok := d.GetOk("iam_instance_profile"); ok { 297 createLaunchConfigurationOpts.IamInstanceProfile = aws.String(v.(string)) 298 } 299 300 if v, ok := d.GetOk("placement_tenancy"); ok { 301 createLaunchConfigurationOpts.PlacementTenancy = aws.String(v.(string)) 302 } 303 304 if v, ok := d.GetOk("associate_public_ip_address"); ok { 305 createLaunchConfigurationOpts.AssociatePublicIpAddress = aws.Bool(v.(bool)) 306 } 307 308 if v, ok := d.GetOk("key_name"); ok { 309 createLaunchConfigurationOpts.KeyName = aws.String(v.(string)) 310 } 311 if v, ok := d.GetOk("spot_price"); ok { 312 createLaunchConfigurationOpts.SpotPrice = aws.String(v.(string)) 313 } 314 315 if v, ok := d.GetOk("security_groups"); ok { 316 createLaunchConfigurationOpts.SecurityGroups = expandStringList( 317 v.(*schema.Set).List(), 318 ) 319 } 320 321 var blockDevices []*autoscaling.BlockDeviceMapping 322 323 if v, ok := d.GetOk("ebs_block_device"); ok { 324 vL := v.(*schema.Set).List() 325 for _, v := range vL { 326 bd := v.(map[string]interface{}) 327 ebs := &autoscaling.Ebs{ 328 DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)), 329 } 330 331 if v, ok := bd["snapshot_id"].(string); ok && v != "" { 332 ebs.SnapshotId = aws.String(v) 333 } 334 335 if v, ok := bd["volume_size"].(int); ok && v != 0 { 336 ebs.VolumeSize = aws.Int64(int64(v)) 337 } 338 339 if v, ok := bd["volume_type"].(string); ok && v != "" { 340 ebs.VolumeType = aws.String(v) 341 } 342 343 if v, ok := bd["iops"].(int); ok && v > 0 { 344 ebs.Iops = aws.Int64(int64(v)) 345 } 346 347 blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{ 348 DeviceName: aws.String(bd["device_name"].(string)), 349 Ebs: ebs, 350 }) 351 } 352 } 353 354 if v, ok := d.GetOk("ephemeral_block_device"); ok { 355 vL := v.(*schema.Set).List() 356 for _, v := range vL { 357 bd := v.(map[string]interface{}) 358 blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{ 359 DeviceName: aws.String(bd["device_name"].(string)), 360 VirtualName: aws.String(bd["virtual_name"].(string)), 361 }) 362 } 363 } 364 365 if v, ok := d.GetOk("root_block_device"); ok { 366 vL := v.(*schema.Set).List() 367 if len(vL) > 1 { 368 return fmt.Errorf("Cannot specify more than one root_block_device.") 369 } 370 for _, v := range vL { 371 bd := v.(map[string]interface{}) 372 ebs := &autoscaling.Ebs{ 373 DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)), 374 } 375 376 if v, ok := bd["volume_size"].(int); ok && v != 0 { 377 ebs.VolumeSize = aws.Int64(int64(v)) 378 } 379 380 if v, ok := bd["volume_type"].(string); ok && v != "" { 381 ebs.VolumeType = aws.String(v) 382 } 383 384 if v, ok := bd["iops"].(int); ok && v > 0 { 385 ebs.Iops = aws.Int64(int64(v)) 386 } 387 388 if dn, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn); err == nil { 389 if dn == nil { 390 return fmt.Errorf( 391 "Expected to find a Root Device name for AMI (%s), but got none", 392 d.Get("image_id").(string)) 393 } 394 blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{ 395 DeviceName: dn, 396 Ebs: ebs, 397 }) 398 } else { 399 return err 400 } 401 } 402 } 403 404 if len(blockDevices) > 0 { 405 createLaunchConfigurationOpts.BlockDeviceMappings = blockDevices 406 } 407 408 var lcName string 409 if v, ok := d.GetOk("name"); ok { 410 lcName = v.(string) 411 } else if v, ok := d.GetOk("name_prefix"); ok { 412 lcName = resource.PrefixedUniqueId(v.(string)) 413 } else { 414 lcName = resource.UniqueId() 415 } 416 createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(lcName) 417 418 log.Printf( 419 "[DEBUG] autoscaling create launch configuration: %s", createLaunchConfigurationOpts) 420 421 // IAM profiles can take ~10 seconds to propagate in AWS: 422 // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console 423 err := resource.Retry(30*time.Second, func() error { 424 _, err := autoscalingconn.CreateLaunchConfiguration(&createLaunchConfigurationOpts) 425 if err != nil { 426 if awsErr, ok := err.(awserr.Error); ok { 427 if awsErr.Message() == "Invalid IamInstanceProfile" { 428 return err 429 } 430 } 431 return &resource.RetryError{ 432 Err: err, 433 } 434 } 435 return nil 436 }) 437 438 if err != nil { 439 return fmt.Errorf("Error creating launch configuration: %s", err) 440 } 441 442 d.SetId(lcName) 443 log.Printf("[INFO] launch configuration ID: %s", d.Id()) 444 445 // We put a Retry here since sometimes eventual consistency bites 446 // us and we need to retry a few times to get the LC to load properly 447 return resource.Retry(30*time.Second, func() error { 448 return resourceAwsLaunchConfigurationRead(d, meta) 449 }) 450 } 451 452 func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}) error { 453 autoscalingconn := meta.(*AWSClient).autoscalingconn 454 ec2conn := meta.(*AWSClient).ec2conn 455 456 describeOpts := autoscaling.DescribeLaunchConfigurationsInput{ 457 LaunchConfigurationNames: []*string{aws.String(d.Id())}, 458 } 459 460 log.Printf("[DEBUG] launch configuration describe configuration: %s", describeOpts) 461 describConfs, err := autoscalingconn.DescribeLaunchConfigurations(&describeOpts) 462 if err != nil { 463 return fmt.Errorf("Error retrieving launch configuration: %s", err) 464 } 465 if len(describConfs.LaunchConfigurations) == 0 { 466 d.SetId("") 467 return nil 468 } 469 470 // Verify AWS returned our launch configuration 471 if *describConfs.LaunchConfigurations[0].LaunchConfigurationName != d.Id() { 472 return fmt.Errorf( 473 "Unable to find launch configuration: %#v", 474 describConfs.LaunchConfigurations) 475 } 476 477 lc := describConfs.LaunchConfigurations[0] 478 479 d.Set("key_name", lc.KeyName) 480 d.Set("image_id", lc.ImageId) 481 d.Set("instance_type", lc.InstanceType) 482 d.Set("name", lc.LaunchConfigurationName) 483 484 d.Set("iam_instance_profile", lc.IamInstanceProfile) 485 d.Set("ebs_optimized", lc.EbsOptimized) 486 d.Set("spot_price", lc.SpotPrice) 487 d.Set("enable_monitoring", lc.InstanceMonitoring.Enabled) 488 d.Set("security_groups", lc.SecurityGroups) 489 490 if err := readLCBlockDevices(d, lc, ec2conn); err != nil { 491 return err 492 } 493 494 return nil 495 } 496 497 func resourceAwsLaunchConfigurationDelete(d *schema.ResourceData, meta interface{}) error { 498 autoscalingconn := meta.(*AWSClient).autoscalingconn 499 500 log.Printf("[DEBUG] Launch Configuration destroy: %v", d.Id()) 501 _, err := autoscalingconn.DeleteLaunchConfiguration( 502 &autoscaling.DeleteLaunchConfigurationInput{ 503 LaunchConfigurationName: aws.String(d.Id()), 504 }) 505 if err != nil { 506 autoscalingerr, ok := err.(awserr.Error) 507 if ok && (autoscalingerr.Code() == "InvalidConfiguration.NotFound" || autoscalingerr.Code() == "ValidationError") { 508 log.Printf("[DEBUG] Launch configuration (%s) not found", d.Id()) 509 return nil 510 } 511 512 return err 513 } 514 515 return nil 516 } 517 518 func readLCBlockDevices(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) error { 519 ibds, err := readBlockDevicesFromLaunchConfiguration(d, lc, ec2conn) 520 if err != nil { 521 return err 522 } 523 524 if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { 525 return err 526 } 527 if err := d.Set("ephemeral_block_device", ibds["ephemeral"]); err != nil { 528 return err 529 } 530 if ibds["root"] != nil { 531 if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil { 532 return err 533 } 534 } else { 535 d.Set("root_block_device", []interface{}{}) 536 } 537 538 return nil 539 } 540 541 func readBlockDevicesFromLaunchConfiguration(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) ( 542 map[string]interface{}, error) { 543 blockDevices := make(map[string]interface{}) 544 blockDevices["ebs"] = make([]map[string]interface{}, 0) 545 blockDevices["ephemeral"] = make([]map[string]interface{}, 0) 546 blockDevices["root"] = nil 547 if len(lc.BlockDeviceMappings) == 0 { 548 return nil, nil 549 } 550 rootDeviceName, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn) 551 if err != nil { 552 return nil, err 553 } 554 if rootDeviceName == nil { 555 // We do this so the value is empty so we don't have to do nil checks later 556 var blank string 557 rootDeviceName = &blank 558 } 559 for _, bdm := range lc.BlockDeviceMappings { 560 bd := make(map[string]interface{}) 561 if bdm.Ebs != nil && bdm.Ebs.DeleteOnTermination != nil { 562 bd["delete_on_termination"] = *bdm.Ebs.DeleteOnTermination 563 } 564 if bdm.Ebs != nil && bdm.Ebs.VolumeSize != nil { 565 bd["volume_size"] = *bdm.Ebs.VolumeSize 566 } 567 if bdm.Ebs != nil && bdm.Ebs.VolumeType != nil { 568 bd["volume_type"] = *bdm.Ebs.VolumeType 569 } 570 if bdm.Ebs != nil && bdm.Ebs.Iops != nil { 571 bd["iops"] = *bdm.Ebs.Iops 572 } 573 if bdm.DeviceName != nil && *bdm.DeviceName == *rootDeviceName { 574 blockDevices["root"] = bd 575 } else { 576 if bdm.DeviceName != nil { 577 bd["device_name"] = *bdm.DeviceName 578 } 579 if bdm.VirtualName != nil { 580 bd["virtual_name"] = *bdm.VirtualName 581 blockDevices["ephemeral"] = append(blockDevices["ephemeral"].([]map[string]interface{}), bd) 582 } else { 583 if bdm.Ebs != nil && bdm.Ebs.SnapshotId != nil { 584 bd["snapshot_id"] = *bdm.Ebs.SnapshotId 585 } 586 blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) 587 } 588 } 589 } 590 return blockDevices, nil 591 }