github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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 blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{ 390 DeviceName: dn, 391 Ebs: ebs, 392 }) 393 } else { 394 return err 395 } 396 } 397 } 398 399 if len(blockDevices) > 0 { 400 createLaunchConfigurationOpts.BlockDeviceMappings = blockDevices 401 } 402 403 var lcName string 404 if v, ok := d.GetOk("name"); ok { 405 lcName = v.(string) 406 } else if v, ok := d.GetOk("name_prefix"); ok { 407 lcName = resource.PrefixedUniqueId(v.(string)) 408 } else { 409 lcName = resource.UniqueId() 410 } 411 createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(lcName) 412 413 log.Printf( 414 "[DEBUG] autoscaling create launch configuration: %s", createLaunchConfigurationOpts) 415 416 // IAM profiles can take ~10 seconds to propagate in AWS: 417 // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console 418 err := resource.Retry(30*time.Second, func() error { 419 _, err := autoscalingconn.CreateLaunchConfiguration(&createLaunchConfigurationOpts) 420 if err != nil { 421 if awsErr, ok := err.(awserr.Error); ok { 422 if awsErr.Message() == "Invalid IamInstanceProfile" { 423 return err 424 } 425 } 426 return &resource.RetryError{ 427 Err: err, 428 } 429 } 430 return nil 431 }) 432 433 if err != nil { 434 return fmt.Errorf("Error creating launch configuration: %s", err) 435 } 436 437 d.SetId(lcName) 438 log.Printf("[INFO] launch configuration ID: %s", d.Id()) 439 440 // We put a Retry here since sometimes eventual consistency bites 441 // us and we need to retry a few times to get the LC to load properly 442 return resource.Retry(30*time.Second, func() error { 443 return resourceAwsLaunchConfigurationRead(d, meta) 444 }) 445 } 446 447 func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}) error { 448 autoscalingconn := meta.(*AWSClient).autoscalingconn 449 ec2conn := meta.(*AWSClient).ec2conn 450 451 describeOpts := autoscaling.DescribeLaunchConfigurationsInput{ 452 LaunchConfigurationNames: []*string{aws.String(d.Id())}, 453 } 454 455 log.Printf("[DEBUG] launch configuration describe configuration: %s", describeOpts) 456 describConfs, err := autoscalingconn.DescribeLaunchConfigurations(&describeOpts) 457 if err != nil { 458 return fmt.Errorf("Error retrieving launch configuration: %s", err) 459 } 460 if len(describConfs.LaunchConfigurations) == 0 { 461 d.SetId("") 462 return nil 463 } 464 465 // Verify AWS returned our launch configuration 466 if *describConfs.LaunchConfigurations[0].LaunchConfigurationName != d.Id() { 467 return fmt.Errorf( 468 "Unable to find launch configuration: %#v", 469 describConfs.LaunchConfigurations) 470 } 471 472 lc := describConfs.LaunchConfigurations[0] 473 474 d.Set("key_name", lc.KeyName) 475 d.Set("image_id", lc.ImageId) 476 d.Set("instance_type", lc.InstanceType) 477 d.Set("name", lc.LaunchConfigurationName) 478 479 d.Set("iam_instance_profile", lc.IamInstanceProfile) 480 d.Set("ebs_optimized", lc.EbsOptimized) 481 d.Set("spot_price", lc.SpotPrice) 482 d.Set("enable_monitoring", lc.InstanceMonitoring.Enabled) 483 d.Set("security_groups", lc.SecurityGroups) 484 485 if err := readLCBlockDevices(d, lc, ec2conn); err != nil { 486 return err 487 } 488 489 return nil 490 } 491 492 func resourceAwsLaunchConfigurationDelete(d *schema.ResourceData, meta interface{}) error { 493 autoscalingconn := meta.(*AWSClient).autoscalingconn 494 495 log.Printf("[DEBUG] Launch Configuration destroy: %v", d.Id()) 496 _, err := autoscalingconn.DeleteLaunchConfiguration( 497 &autoscaling.DeleteLaunchConfigurationInput{ 498 LaunchConfigurationName: aws.String(d.Id()), 499 }) 500 if err != nil { 501 autoscalingerr, ok := err.(awserr.Error) 502 if ok && (autoscalingerr.Code() == "InvalidConfiguration.NotFound" || autoscalingerr.Code() == "ValidationError") { 503 log.Printf("[DEBUG] Launch configuration (%s) not found", d.Id()) 504 return nil 505 } 506 507 return err 508 } 509 510 return nil 511 } 512 513 func readLCBlockDevices(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) error { 514 ibds, err := readBlockDevicesFromLaunchConfiguration(d, lc, ec2conn) 515 if err != nil { 516 return err 517 } 518 519 if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { 520 return err 521 } 522 if err := d.Set("ephemeral_block_device", ibds["ephemeral"]); err != nil { 523 return err 524 } 525 if ibds["root"] != nil { 526 if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil { 527 return err 528 } 529 } else { 530 d.Set("root_block_device", []interface{}{}) 531 } 532 533 return nil 534 } 535 536 func readBlockDevicesFromLaunchConfiguration(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) ( 537 map[string]interface{}, error) { 538 blockDevices := make(map[string]interface{}) 539 blockDevices["ebs"] = make([]map[string]interface{}, 0) 540 blockDevices["ephemeral"] = make([]map[string]interface{}, 0) 541 blockDevices["root"] = nil 542 if len(lc.BlockDeviceMappings) == 0 { 543 return nil, nil 544 } 545 rootDeviceName, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn) 546 if err != nil { 547 return nil, err 548 } 549 if rootDeviceName == nil { 550 // We do this so the value is empty so we don't have to do nil checks later 551 var blank string 552 rootDeviceName = &blank 553 } 554 for _, bdm := range lc.BlockDeviceMappings { 555 bd := make(map[string]interface{}) 556 if bdm.Ebs != nil && bdm.Ebs.DeleteOnTermination != nil { 557 bd["delete_on_termination"] = *bdm.Ebs.DeleteOnTermination 558 } 559 if bdm.Ebs != nil && bdm.Ebs.VolumeSize != nil { 560 bd["volume_size"] = *bdm.Ebs.VolumeSize 561 } 562 if bdm.Ebs != nil && bdm.Ebs.VolumeType != nil { 563 bd["volume_type"] = *bdm.Ebs.VolumeType 564 } 565 if bdm.Ebs != nil && bdm.Ebs.Iops != nil { 566 bd["iops"] = *bdm.Ebs.Iops 567 } 568 if bdm.DeviceName != nil && *bdm.DeviceName == *rootDeviceName { 569 blockDevices["root"] = bd 570 } else { 571 if bdm.DeviceName != nil { 572 bd["device_name"] = *bdm.DeviceName 573 } 574 if bdm.VirtualName != nil { 575 bd["virtual_name"] = *bdm.VirtualName 576 blockDevices["ephemeral"] = append(blockDevices["ephemeral"].([]map[string]interface{}), bd) 577 } else { 578 if bdm.Ebs != nil && bdm.Ebs.SnapshotId != nil { 579 bd["snapshot_id"] = *bdm.Ebs.SnapshotId 580 } 581 blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) 582 } 583 } 584 } 585 return blockDevices, nil 586 }