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