github.com/ricardclau/terraform@v0.6.17-0.20160519222547-283e3ae6b5a9/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 if v, ok := d.GetOk("ebs_block_device"); ok { 335 vL := v.(*schema.Set).List() 336 for _, v := range vL { 337 bd := v.(map[string]interface{}) 338 ebs := &autoscaling.Ebs{ 339 DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)), 340 } 341 342 if v, ok := bd["snapshot_id"].(string); ok && v != "" { 343 ebs.SnapshotId = aws.String(v) 344 } 345 346 if v, ok := bd["encrypted"].(bool); ok && v { 347 ebs.Encrypted = aws.Bool(v) 348 } 349 350 if v, ok := bd["volume_size"].(int); ok && v != 0 { 351 ebs.VolumeSize = aws.Int64(int64(v)) 352 } 353 354 if v, ok := bd["volume_type"].(string); ok && v != "" { 355 ebs.VolumeType = aws.String(v) 356 } 357 358 if v, ok := bd["iops"].(int); ok && v > 0 { 359 ebs.Iops = aws.Int64(int64(v)) 360 } 361 362 blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{ 363 DeviceName: aws.String(bd["device_name"].(string)), 364 Ebs: ebs, 365 }) 366 } 367 } 368 369 if v, ok := d.GetOk("ephemeral_block_device"); ok { 370 vL := v.(*schema.Set).List() 371 for _, v := range vL { 372 bd := v.(map[string]interface{}) 373 blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{ 374 DeviceName: aws.String(bd["device_name"].(string)), 375 VirtualName: aws.String(bd["virtual_name"].(string)), 376 }) 377 } 378 } 379 380 if v, ok := d.GetOk("root_block_device"); ok { 381 vL := v.(*schema.Set).List() 382 if len(vL) > 1 { 383 return fmt.Errorf("Cannot specify more than one root_block_device.") 384 } 385 for _, v := range vL { 386 bd := v.(map[string]interface{}) 387 ebs := &autoscaling.Ebs{ 388 DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)), 389 } 390 391 if v, ok := bd["volume_size"].(int); ok && v != 0 { 392 ebs.VolumeSize = aws.Int64(int64(v)) 393 } 394 395 if v, ok := bd["volume_type"].(string); ok && v != "" { 396 ebs.VolumeType = aws.String(v) 397 } 398 399 if v, ok := bd["iops"].(int); ok && v > 0 { 400 ebs.Iops = aws.Int64(int64(v)) 401 } 402 403 if dn, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn); err == nil { 404 if dn == nil { 405 return fmt.Errorf( 406 "Expected to find a Root Device name for AMI (%s), but got none", 407 d.Get("image_id").(string)) 408 } 409 blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{ 410 DeviceName: dn, 411 Ebs: ebs, 412 }) 413 } else { 414 return err 415 } 416 } 417 } 418 419 if len(blockDevices) > 0 { 420 createLaunchConfigurationOpts.BlockDeviceMappings = blockDevices 421 } 422 423 var lcName string 424 if v, ok := d.GetOk("name"); ok { 425 lcName = v.(string) 426 } else if v, ok := d.GetOk("name_prefix"); ok { 427 lcName = resource.PrefixedUniqueId(v.(string)) 428 } else { 429 lcName = resource.UniqueId() 430 } 431 createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(lcName) 432 433 log.Printf( 434 "[DEBUG] autoscaling create launch configuration: %s", createLaunchConfigurationOpts) 435 436 // IAM profiles can take ~10 seconds to propagate in AWS: 437 // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console 438 err := resource.Retry(30*time.Second, func() *resource.RetryError { 439 _, err := autoscalingconn.CreateLaunchConfiguration(&createLaunchConfigurationOpts) 440 if err != nil { 441 if awsErr, ok := err.(awserr.Error); ok { 442 if strings.Contains(awsErr.Message(), "Invalid IamInstanceProfile") { 443 return resource.RetryableError(err) 444 } 445 } 446 return resource.NonRetryableError(err) 447 } 448 return nil 449 }) 450 451 if err != nil { 452 return fmt.Errorf("Error creating launch configuration: %s", err) 453 } 454 455 d.SetId(lcName) 456 log.Printf("[INFO] launch configuration ID: %s", d.Id()) 457 458 // We put a Retry here since sometimes eventual consistency bites 459 // us and we need to retry a few times to get the LC to load properly 460 return resource.Retry(30*time.Second, func() *resource.RetryError { 461 err := resourceAwsLaunchConfigurationRead(d, meta) 462 if err != nil { 463 return resource.RetryableError(err) 464 } 465 return nil 466 }) 467 } 468 469 func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}) error { 470 autoscalingconn := meta.(*AWSClient).autoscalingconn 471 ec2conn := meta.(*AWSClient).ec2conn 472 473 describeOpts := autoscaling.DescribeLaunchConfigurationsInput{ 474 LaunchConfigurationNames: []*string{aws.String(d.Id())}, 475 } 476 477 log.Printf("[DEBUG] launch configuration describe configuration: %s", describeOpts) 478 describConfs, err := autoscalingconn.DescribeLaunchConfigurations(&describeOpts) 479 if err != nil { 480 return fmt.Errorf("Error retrieving launch configuration: %s", err) 481 } 482 if len(describConfs.LaunchConfigurations) == 0 { 483 d.SetId("") 484 return nil 485 } 486 487 // Verify AWS returned our launch configuration 488 if *describConfs.LaunchConfigurations[0].LaunchConfigurationName != d.Id() { 489 return fmt.Errorf( 490 "Unable to find launch configuration: %#v", 491 describConfs.LaunchConfigurations) 492 } 493 494 lc := describConfs.LaunchConfigurations[0] 495 496 d.Set("key_name", lc.KeyName) 497 d.Set("image_id", lc.ImageId) 498 d.Set("instance_type", lc.InstanceType) 499 d.Set("name", lc.LaunchConfigurationName) 500 501 d.Set("iam_instance_profile", lc.IamInstanceProfile) 502 d.Set("ebs_optimized", lc.EbsOptimized) 503 d.Set("spot_price", lc.SpotPrice) 504 d.Set("enable_monitoring", lc.InstanceMonitoring.Enabled) 505 d.Set("security_groups", lc.SecurityGroups) 506 507 if err := readLCBlockDevices(d, lc, ec2conn); err != nil { 508 return err 509 } 510 511 return nil 512 } 513 514 func resourceAwsLaunchConfigurationDelete(d *schema.ResourceData, meta interface{}) error { 515 autoscalingconn := meta.(*AWSClient).autoscalingconn 516 517 log.Printf("[DEBUG] Launch Configuration destroy: %v", d.Id()) 518 _, err := autoscalingconn.DeleteLaunchConfiguration( 519 &autoscaling.DeleteLaunchConfigurationInput{ 520 LaunchConfigurationName: aws.String(d.Id()), 521 }) 522 if err != nil { 523 autoscalingerr, ok := err.(awserr.Error) 524 if ok && (autoscalingerr.Code() == "InvalidConfiguration.NotFound" || autoscalingerr.Code() == "ValidationError") { 525 log.Printf("[DEBUG] Launch configuration (%s) not found", d.Id()) 526 return nil 527 } 528 529 return err 530 } 531 532 return nil 533 } 534 535 func readLCBlockDevices(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) error { 536 ibds, err := readBlockDevicesFromLaunchConfiguration(d, lc, ec2conn) 537 if err != nil { 538 return err 539 } 540 541 if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { 542 return err 543 } 544 if err := d.Set("ephemeral_block_device", ibds["ephemeral"]); err != nil { 545 return err 546 } 547 if ibds["root"] != nil { 548 if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil { 549 return err 550 } 551 } else { 552 d.Set("root_block_device", []interface{}{}) 553 } 554 555 return nil 556 } 557 558 func readBlockDevicesFromLaunchConfiguration(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) ( 559 map[string]interface{}, error) { 560 blockDevices := make(map[string]interface{}) 561 blockDevices["ebs"] = make([]map[string]interface{}, 0) 562 blockDevices["ephemeral"] = make([]map[string]interface{}, 0) 563 blockDevices["root"] = nil 564 if len(lc.BlockDeviceMappings) == 0 { 565 return nil, nil 566 } 567 rootDeviceName, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn) 568 if err != nil { 569 return nil, err 570 } 571 if rootDeviceName == nil { 572 // We do this so the value is empty so we don't have to do nil checks later 573 var blank string 574 rootDeviceName = &blank 575 } 576 for _, bdm := range lc.BlockDeviceMappings { 577 bd := make(map[string]interface{}) 578 if bdm.Ebs != nil && bdm.Ebs.DeleteOnTermination != nil { 579 bd["delete_on_termination"] = *bdm.Ebs.DeleteOnTermination 580 } 581 if bdm.Ebs != nil && bdm.Ebs.VolumeSize != nil { 582 bd["volume_size"] = *bdm.Ebs.VolumeSize 583 } 584 if bdm.Ebs != nil && bdm.Ebs.VolumeType != nil { 585 bd["volume_type"] = *bdm.Ebs.VolumeType 586 } 587 if bdm.Ebs != nil && bdm.Ebs.Iops != nil { 588 bd["iops"] = *bdm.Ebs.Iops 589 } 590 if bdm.Ebs != nil && bdm.Ebs.Encrypted != nil { 591 bd["encrypted"] = *bdm.Ebs.Encrypted 592 } 593 if bdm.DeviceName != nil && *bdm.DeviceName == *rootDeviceName { 594 blockDevices["root"] = bd 595 } else { 596 if bdm.DeviceName != nil { 597 bd["device_name"] = *bdm.DeviceName 598 } 599 if bdm.VirtualName != nil { 600 bd["virtual_name"] = *bdm.VirtualName 601 blockDevices["ephemeral"] = append(blockDevices["ephemeral"].([]map[string]interface{}), bd) 602 } else { 603 if bdm.Ebs != nil && bdm.Ebs.SnapshotId != nil { 604 bd["snapshot_id"] = *bdm.Ebs.SnapshotId 605 } 606 blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) 607 } 608 } 609 } 610 return blockDevices, nil 611 }