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