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