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