github.com/i0n/terraform@v0.4.3-0.20150506151324-010a39a58ec1/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/awslabs/aws-sdk-go/aws" 13 "github.com/awslabs/aws-sdk-go/service/autoscaling" 14 "github.com/awslabs/aws-sdk-go/service/ec2" 15 "github.com/hashicorp/terraform/helper/hashcode" 16 "github.com/hashicorp/terraform/helper/resource" 17 "github.com/hashicorp/terraform/helper/schema" 18 ) 19 20 func resourceAwsLaunchConfiguration() *schema.Resource { 21 return &schema.Resource{ 22 Create: resourceAwsLaunchConfigurationCreate, 23 Read: resourceAwsLaunchConfigurationRead, 24 Delete: resourceAwsLaunchConfigurationDelete, 25 26 Schema: map[string]*schema.Schema{ 27 "name": &schema.Schema{ 28 Type: schema.TypeString, 29 Optional: true, 30 Computed: true, 31 ForceNew: true, 32 }, 33 34 "image_id": &schema.Schema{ 35 Type: schema.TypeString, 36 Required: true, 37 ForceNew: true, 38 }, 39 40 "instance_type": &schema.Schema{ 41 Type: schema.TypeString, 42 Required: true, 43 ForceNew: true, 44 }, 45 46 "iam_instance_profile": &schema.Schema{ 47 Type: schema.TypeString, 48 Optional: true, 49 ForceNew: true, 50 }, 51 52 "key_name": &schema.Schema{ 53 Type: schema.TypeString, 54 Optional: true, 55 Computed: true, 56 ForceNew: true, 57 }, 58 59 "user_data": &schema.Schema{ 60 Type: schema.TypeString, 61 Optional: true, 62 ForceNew: true, 63 StateFunc: func(v interface{}) string { 64 switch v.(type) { 65 case string: 66 hash := sha1.Sum([]byte(v.(string))) 67 return hex.EncodeToString(hash[:]) 68 default: 69 return "" 70 } 71 }, 72 }, 73 74 "security_groups": &schema.Schema{ 75 Type: schema.TypeSet, 76 Optional: true, 77 ForceNew: true, 78 Elem: &schema.Schema{Type: schema.TypeString}, 79 Set: schema.HashString, 80 }, 81 82 "associate_public_ip_address": &schema.Schema{ 83 Type: schema.TypeBool, 84 Optional: true, 85 ForceNew: true, 86 Default: false, 87 }, 88 89 "spot_price": &schema.Schema{ 90 Type: schema.TypeString, 91 Optional: true, 92 ForceNew: true, 93 }, 94 95 "ebs_optimized": &schema.Schema{ 96 Type: schema.TypeBool, 97 Optional: true, 98 ForceNew: true, 99 Computed: true, 100 }, 101 102 "placement_tenancy": &schema.Schema{ 103 Type: schema.TypeString, 104 Optional: true, 105 ForceNew: true, 106 }, 107 108 "ebs_block_device": &schema.Schema{ 109 Type: schema.TypeSet, 110 Optional: true, 111 Computed: true, 112 Elem: &schema.Resource{ 113 Schema: map[string]*schema.Schema{ 114 "delete_on_termination": &schema.Schema{ 115 Type: schema.TypeBool, 116 Optional: true, 117 Default: true, 118 ForceNew: true, 119 }, 120 121 "device_name": &schema.Schema{ 122 Type: schema.TypeString, 123 Required: true, 124 ForceNew: true, 125 }, 126 127 "iops": &schema.Schema{ 128 Type: schema.TypeInt, 129 Optional: true, 130 Computed: true, 131 ForceNew: true, 132 }, 133 134 "snapshot_id": &schema.Schema{ 135 Type: schema.TypeString, 136 Optional: true, 137 Computed: true, 138 ForceNew: true, 139 }, 140 141 "volume_size": &schema.Schema{ 142 Type: schema.TypeInt, 143 Optional: true, 144 Computed: true, 145 ForceNew: true, 146 }, 147 148 "volume_type": &schema.Schema{ 149 Type: schema.TypeString, 150 Optional: true, 151 Computed: true, 152 ForceNew: true, 153 }, 154 }, 155 }, 156 Set: func(v interface{}) int { 157 var buf bytes.Buffer 158 m := v.(map[string]interface{}) 159 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 160 buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string))) 161 return hashcode.String(buf.String()) 162 }, 163 }, 164 165 "ephemeral_block_device": &schema.Schema{ 166 Type: schema.TypeSet, 167 Optional: true, 168 ForceNew: true, 169 Elem: &schema.Resource{ 170 Schema: map[string]*schema.Schema{ 171 "device_name": &schema.Schema{ 172 Type: schema.TypeString, 173 Required: true, 174 }, 175 176 "virtual_name": &schema.Schema{ 177 Type: schema.TypeString, 178 Required: true, 179 }, 180 }, 181 }, 182 Set: func(v interface{}) int { 183 var buf bytes.Buffer 184 m := v.(map[string]interface{}) 185 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 186 buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string))) 187 return hashcode.String(buf.String()) 188 }, 189 }, 190 191 "root_block_device": &schema.Schema{ 192 // TODO: This is a set because we don't support singleton 193 // sub-resources today. We'll enforce that the set only ever has 194 // length zero or one below. When TF gains support for 195 // sub-resources this can be converted. 196 Type: schema.TypeSet, 197 Optional: true, 198 Computed: true, 199 Elem: &schema.Resource{ 200 // "You can only modify the volume size, volume type, and Delete on 201 // Termination flag on the block device mapping entry for the root 202 // device volume." - bit.ly/ec2bdmap 203 Schema: map[string]*schema.Schema{ 204 "delete_on_termination": &schema.Schema{ 205 Type: schema.TypeBool, 206 Optional: true, 207 Default: true, 208 ForceNew: true, 209 }, 210 211 "iops": &schema.Schema{ 212 Type: schema.TypeInt, 213 Optional: true, 214 Computed: true, 215 ForceNew: true, 216 }, 217 218 "volume_size": &schema.Schema{ 219 Type: schema.TypeInt, 220 Optional: true, 221 Computed: true, 222 ForceNew: true, 223 }, 224 225 "volume_type": &schema.Schema{ 226 Type: schema.TypeString, 227 Optional: true, 228 Computed: true, 229 ForceNew: true, 230 }, 231 }, 232 }, 233 Set: func(v interface{}) int { 234 // there can be only one root device; no need to hash anything 235 return 0 236 }, 237 }, 238 }, 239 } 240 } 241 242 func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface{}) error { 243 autoscalingconn := meta.(*AWSClient).autoscalingconn 244 ec2conn := meta.(*AWSClient).ec2conn 245 246 createLaunchConfigurationOpts := autoscaling.CreateLaunchConfigurationInput{ 247 LaunchConfigurationName: aws.String(d.Get("name").(string)), 248 ImageID: aws.String(d.Get("image_id").(string)), 249 InstanceType: aws.String(d.Get("instance_type").(string)), 250 EBSOptimized: aws.Boolean(d.Get("ebs_optimized").(bool)), 251 } 252 253 if v, ok := d.GetOk("user_data"); ok { 254 userData := base64.StdEncoding.EncodeToString([]byte(v.(string))) 255 createLaunchConfigurationOpts.UserData = aws.String(userData) 256 } 257 258 if v, ok := d.GetOk("iam_instance_profile"); ok { 259 createLaunchConfigurationOpts.IAMInstanceProfile = aws.String(v.(string)) 260 } 261 262 if v, ok := d.GetOk("placement_tenancy"); ok { 263 createLaunchConfigurationOpts.PlacementTenancy = aws.String(v.(string)) 264 } 265 266 if v, ok := d.GetOk("associate_public_ip_address"); ok { 267 createLaunchConfigurationOpts.AssociatePublicIPAddress = aws.Boolean(v.(bool)) 268 } 269 270 if v, ok := d.GetOk("key_name"); ok { 271 createLaunchConfigurationOpts.KeyName = aws.String(v.(string)) 272 } 273 if v, ok := d.GetOk("spot_price"); ok { 274 createLaunchConfigurationOpts.SpotPrice = aws.String(v.(string)) 275 } 276 277 if v, ok := d.GetOk("security_groups"); ok { 278 createLaunchConfigurationOpts.SecurityGroups = expandStringList( 279 v.(*schema.Set).List(), 280 ) 281 } 282 283 var blockDevices []*autoscaling.BlockDeviceMapping 284 285 if v, ok := d.GetOk("ebs_block_device"); ok { 286 vL := v.(*schema.Set).List() 287 for _, v := range vL { 288 bd := v.(map[string]interface{}) 289 ebs := &autoscaling.EBS{ 290 DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), 291 } 292 293 if v, ok := bd["snapshot_id"].(string); ok && v != "" { 294 ebs.SnapshotID = aws.String(v) 295 } 296 297 if v, ok := bd["volume_size"].(int); ok && v != 0 { 298 ebs.VolumeSize = aws.Long(int64(v)) 299 } 300 301 if v, ok := bd["volume_type"].(string); ok && v != "" { 302 ebs.VolumeType = aws.String(v) 303 } 304 305 if v, ok := bd["iops"].(int); ok && v > 0 { 306 ebs.IOPS = aws.Long(int64(v)) 307 } 308 309 blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{ 310 DeviceName: aws.String(bd["device_name"].(string)), 311 EBS: ebs, 312 }) 313 } 314 } 315 316 if v, ok := d.GetOk("ephemeral_block_device"); ok { 317 vL := v.(*schema.Set).List() 318 for _, v := range vL { 319 bd := v.(map[string]interface{}) 320 blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{ 321 DeviceName: aws.String(bd["device_name"].(string)), 322 VirtualName: aws.String(bd["virtual_name"].(string)), 323 }) 324 } 325 } 326 327 if v, ok := d.GetOk("root_block_device"); ok { 328 vL := v.(*schema.Set).List() 329 if len(vL) > 1 { 330 return fmt.Errorf("Cannot specify more than one root_block_device.") 331 } 332 for _, v := range vL { 333 bd := v.(map[string]interface{}) 334 ebs := &autoscaling.EBS{ 335 DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), 336 } 337 338 if v, ok := bd["volume_size"].(int); ok && v != 0 { 339 ebs.VolumeSize = aws.Long(int64(v)) 340 } 341 342 if v, ok := bd["volume_type"].(string); ok && v != "" { 343 ebs.VolumeType = aws.String(v) 344 } 345 346 if v, ok := bd["iops"].(int); ok && v > 0 { 347 ebs.IOPS = aws.Long(int64(v)) 348 } 349 350 if dn, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn); err == nil { 351 blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{ 352 DeviceName: dn, 353 EBS: ebs, 354 }) 355 } else { 356 return err 357 } 358 } 359 } 360 361 if len(blockDevices) > 0 { 362 createLaunchConfigurationOpts.BlockDeviceMappings = blockDevices 363 } 364 365 var lcName string 366 if v, ok := d.GetOk("name"); ok { 367 lcName = v.(string) 368 } else { 369 lcName = resource.UniqueId() 370 } 371 createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(lcName) 372 373 log.Printf( 374 "[DEBUG] autoscaling create launch configuration: %#v", createLaunchConfigurationOpts) 375 _, err := autoscalingconn.CreateLaunchConfiguration(&createLaunchConfigurationOpts) 376 if err != nil { 377 return fmt.Errorf("Error creating launch configuration: %s", err) 378 } 379 380 d.SetId(lcName) 381 log.Printf("[INFO] launch configuration ID: %s", d.Id()) 382 383 // We put a Retry here since sometimes eventual consistency bites 384 // us and we need to retry a few times to get the LC to load properly 385 return resource.Retry(30*time.Second, func() error { 386 return resourceAwsLaunchConfigurationRead(d, meta) 387 }) 388 } 389 390 func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}) error { 391 autoscalingconn := meta.(*AWSClient).autoscalingconn 392 ec2conn := meta.(*AWSClient).ec2conn 393 394 describeOpts := autoscaling.DescribeLaunchConfigurationsInput{ 395 LaunchConfigurationNames: []*string{aws.String(d.Id())}, 396 } 397 398 log.Printf("[DEBUG] launch configuration describe configuration: %#v", describeOpts) 399 describConfs, err := autoscalingconn.DescribeLaunchConfigurations(&describeOpts) 400 if err != nil { 401 return fmt.Errorf("Error retrieving launch configuration: %s", err) 402 } 403 if len(describConfs.LaunchConfigurations) == 0 { 404 d.SetId("") 405 return nil 406 } 407 408 // Verify AWS returned our launch configuration 409 if *describConfs.LaunchConfigurations[0].LaunchConfigurationName != d.Id() { 410 return fmt.Errorf( 411 "Unable to find launch configuration: %#v", 412 describConfs.LaunchConfigurations) 413 } 414 415 lc := describConfs.LaunchConfigurations[0] 416 417 d.Set("key_name", lc.KeyName) 418 d.Set("image_id", lc.ImageID) 419 d.Set("instance_type", lc.InstanceType) 420 d.Set("name", lc.LaunchConfigurationName) 421 422 d.Set("iam_instance_profile", lc.IAMInstanceProfile) 423 d.Set("ebs_optimized", lc.EBSOptimized) 424 d.Set("spot_price", lc.SpotPrice) 425 d.Set("security_groups", lc.SecurityGroups) 426 427 if err := readLCBlockDevices(d, lc, ec2conn); err != nil { 428 return err 429 } 430 431 return nil 432 } 433 434 func resourceAwsLaunchConfigurationDelete(d *schema.ResourceData, meta interface{}) error { 435 autoscalingconn := meta.(*AWSClient).autoscalingconn 436 437 log.Printf("[DEBUG] Launch Configuration destroy: %v", d.Id()) 438 _, err := autoscalingconn.DeleteLaunchConfiguration( 439 &autoscaling.DeleteLaunchConfigurationInput{ 440 LaunchConfigurationName: aws.String(d.Id()), 441 }) 442 if err != nil { 443 autoscalingerr, ok := err.(aws.APIError) 444 if ok && autoscalingerr.Code == "InvalidConfiguration.NotFound" { 445 return nil 446 } 447 448 return err 449 } 450 451 return nil 452 } 453 454 func readLCBlockDevices(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) error { 455 ibds, err := readBlockDevicesFromLaunchConfiguration(d, lc, ec2conn) 456 if err != nil { 457 return err 458 } 459 460 if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { 461 return err 462 } 463 if err := d.Set("ephemeral_block_device", ibds["ephemeral"]); err != nil { 464 return err 465 } 466 if ibds["root"] != nil { 467 if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil { 468 return err 469 } 470 } else { 471 d.Set("root_block_device", []interface{}{}) 472 } 473 474 return nil 475 } 476 477 func readBlockDevicesFromLaunchConfiguration(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) ( 478 map[string]interface{}, error) { 479 blockDevices := make(map[string]interface{}) 480 blockDevices["ebs"] = make([]map[string]interface{}, 0) 481 blockDevices["ephemeral"] = make([]map[string]interface{}, 0) 482 blockDevices["root"] = nil 483 if len(lc.BlockDeviceMappings) == 0 { 484 return nil, nil 485 } 486 rootDeviceName, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn) 487 if err != nil { 488 return nil, err 489 } 490 for _, bdm := range lc.BlockDeviceMappings { 491 bd := make(map[string]interface{}) 492 if bdm.EBS != nil && bdm.EBS.DeleteOnTermination != nil { 493 bd["delete_on_termination"] = *bdm.EBS.DeleteOnTermination 494 } 495 if bdm.EBS != nil && bdm.EBS.VolumeSize != nil { 496 bd["volume_size"] = *bdm.EBS.VolumeSize 497 } 498 if bdm.EBS != nil && bdm.EBS.VolumeType != nil { 499 bd["volume_type"] = *bdm.EBS.VolumeType 500 } 501 if bdm.EBS != nil && bdm.EBS.IOPS != nil { 502 bd["iops"] = *bdm.EBS.IOPS 503 } 504 if bdm.DeviceName != nil && *bdm.DeviceName == *rootDeviceName { 505 blockDevices["root"] = bd 506 } else { 507 if bdm.DeviceName != nil { 508 bd["device_name"] = *bdm.DeviceName 509 } 510 if bdm.VirtualName != nil { 511 bd["virtual_name"] = *bdm.VirtualName 512 blockDevices["ephemeral"] = append(blockDevices["ephemeral"].([]map[string]interface{}), bd) 513 } else { 514 if bdm.EBS != nil && bdm.EBS.SnapshotID != nil { 515 bd["snapshot_id"] = *bdm.EBS.SnapshotID 516 } 517 blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) 518 } 519 } 520 } 521 return blockDevices, nil 522 }