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