github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/builtin/providers/aws/resource_aws_instance.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "crypto/sha1" 6 "encoding/hex" 7 "fmt" 8 "log" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/hashicorp/terraform/helper/hashcode" 14 "github.com/hashicorp/terraform/helper/resource" 15 "github.com/hashicorp/terraform/helper/schema" 16 "github.com/mitchellh/goamz/ec2" 17 ) 18 19 func resourceAwsInstance() *schema.Resource { 20 return &schema.Resource{ 21 Create: resourceAwsInstanceCreate, 22 Read: resourceAwsInstanceRead, 23 Update: resourceAwsInstanceUpdate, 24 Delete: resourceAwsInstanceDelete, 25 26 Schema: map[string]*schema.Schema{ 27 "ami": &schema.Schema{ 28 Type: schema.TypeString, 29 Required: true, 30 ForceNew: true, 31 }, 32 33 "associate_public_ip_address": &schema.Schema{ 34 Type: schema.TypeBool, 35 Optional: true, 36 ForceNew: true, 37 }, 38 39 "availability_zone": &schema.Schema{ 40 Type: schema.TypeString, 41 Optional: true, 42 Computed: true, 43 ForceNew: true, 44 }, 45 46 "instance_type": &schema.Schema{ 47 Type: schema.TypeString, 48 Required: true, 49 ForceNew: true, 50 }, 51 52 "key_name": &schema.Schema{ 53 Type: schema.TypeString, 54 Optional: true, 55 ForceNew: true, 56 Computed: true, 57 }, 58 59 "subnet_id": &schema.Schema{ 60 Type: schema.TypeString, 61 Optional: true, 62 Computed: true, 63 ForceNew: true, 64 }, 65 66 "private_ip": &schema.Schema{ 67 Type: schema.TypeString, 68 Optional: true, 69 ForceNew: true, 70 Computed: true, 71 }, 72 73 "source_dest_check": &schema.Schema{ 74 Type: schema.TypeBool, 75 Optional: true, 76 }, 77 78 "user_data": &schema.Schema{ 79 Type: schema.TypeString, 80 Optional: true, 81 ForceNew: true, 82 StateFunc: func(v interface{}) string { 83 switch v.(type) { 84 case string: 85 hash := sha1.Sum([]byte(v.(string))) 86 return hex.EncodeToString(hash[:]) 87 default: 88 return "" 89 } 90 }, 91 }, 92 93 "security_groups": &schema.Schema{ 94 Type: schema.TypeSet, 95 Optional: true, 96 Computed: true, 97 ForceNew: true, 98 Elem: &schema.Schema{Type: schema.TypeString}, 99 Set: func(v interface{}) int { 100 return hashcode.String(v.(string)) 101 }, 102 }, 103 104 "public_dns": &schema.Schema{ 105 Type: schema.TypeString, 106 Computed: true, 107 }, 108 109 "public_ip": &schema.Schema{ 110 Type: schema.TypeString, 111 Computed: true, 112 }, 113 114 "private_dns": &schema.Schema{ 115 Type: schema.TypeString, 116 Computed: true, 117 }, 118 119 "ebs_optimized": &schema.Schema{ 120 Type: schema.TypeBool, 121 Optional: true, 122 }, 123 124 "iam_instance_profile": &schema.Schema{ 125 Type: schema.TypeString, 126 ForceNew: true, 127 Optional: true, 128 }, 129 "tenancy": &schema.Schema{ 130 Type: schema.TypeString, 131 Optional: true, 132 Computed: true, 133 ForceNew: true, 134 }, 135 "tags": tagsSchema(), 136 137 "block_device": &schema.Schema{ 138 Type: schema.TypeSet, 139 Optional: true, 140 Computed: true, 141 Elem: &schema.Resource{ 142 Schema: map[string]*schema.Schema{ 143 "device_name": &schema.Schema{ 144 Type: schema.TypeString, 145 Required: true, 146 ForceNew: true, 147 }, 148 149 "virtual_name": &schema.Schema{ 150 Type: schema.TypeString, 151 Optional: true, 152 ForceNew: true, 153 }, 154 155 "snapshot_id": &schema.Schema{ 156 Type: schema.TypeString, 157 Optional: true, 158 Computed: true, 159 ForceNew: true, 160 }, 161 162 "volume_type": &schema.Schema{ 163 Type: schema.TypeString, 164 Optional: true, 165 Computed: true, 166 ForceNew: true, 167 }, 168 169 "volume_size": &schema.Schema{ 170 Type: schema.TypeInt, 171 Optional: true, 172 Computed: true, 173 ForceNew: true, 174 }, 175 176 "delete_on_termination": &schema.Schema{ 177 Type: schema.TypeBool, 178 Optional: true, 179 Default: true, 180 ForceNew: true, 181 }, 182 183 "encrypted": &schema.Schema{ 184 Type: schema.TypeBool, 185 Optional: true, 186 Computed: true, 187 ForceNew: true, 188 }, 189 }, 190 }, 191 Set: resourceAwsInstanceBlockDevicesHash, 192 }, 193 }, 194 } 195 } 196 197 func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { 198 ec2conn := meta.(*AWSClient).ec2conn 199 200 // Figure out user data 201 userData := "" 202 if v := d.Get("user_data"); v != nil { 203 userData = v.(string) 204 } 205 206 associatePublicIPAddress := false 207 if v := d.Get("associate_public_ip_address"); v != nil { 208 associatePublicIPAddress = v.(bool) 209 } 210 211 // Build the creation struct 212 runOpts := &ec2.RunInstances{ 213 ImageId: d.Get("ami").(string), 214 AvailZone: d.Get("availability_zone").(string), 215 InstanceType: d.Get("instance_type").(string), 216 KeyName: d.Get("key_name").(string), 217 SubnetId: d.Get("subnet_id").(string), 218 PrivateIPAddress: d.Get("private_ip").(string), 219 AssociatePublicIpAddress: associatePublicIPAddress, 220 UserData: []byte(userData), 221 EbsOptimized: d.Get("ebs_optimized").(bool), 222 IamInstanceProfile: d.Get("iam_instance_profile").(string), 223 Tenancy: d.Get("tenancy").(string), 224 } 225 226 if v := d.Get("security_groups"); v != nil { 227 for _, v := range v.(*schema.Set).List() { 228 str := v.(string) 229 230 var g ec2.SecurityGroup 231 if runOpts.SubnetId != "" { 232 g.Id = str 233 } else { 234 g.Name = str 235 } 236 237 runOpts.SecurityGroups = append(runOpts.SecurityGroups, g) 238 } 239 } 240 241 if v := d.Get("block_device"); v != nil { 242 vs := v.(*schema.Set).List() 243 if len(vs) > 0 { 244 runOpts.BlockDevices = make([]ec2.BlockDeviceMapping, len(vs)) 245 for i, v := range vs { 246 bd := v.(map[string]interface{}) 247 runOpts.BlockDevices[i].DeviceName = bd["device_name"].(string) 248 runOpts.BlockDevices[i].VirtualName = bd["virtual_name"].(string) 249 runOpts.BlockDevices[i].SnapshotId = bd["snapshot_id"].(string) 250 runOpts.BlockDevices[i].VolumeType = bd["volume_type"].(string) 251 runOpts.BlockDevices[i].VolumeSize = int64(bd["volume_size"].(int)) 252 runOpts.BlockDevices[i].DeleteOnTermination = bd["delete_on_termination"].(bool) 253 runOpts.BlockDevices[i].Encrypted = bd["encrypted"].(bool) 254 } 255 } 256 } 257 258 // Create the instance 259 log.Printf("[DEBUG] Run configuration: %#v", runOpts) 260 runResp, err := ec2conn.RunInstances(runOpts) 261 if err != nil { 262 return fmt.Errorf("Error launching source instance: %s", err) 263 } 264 265 instance := &runResp.Instances[0] 266 log.Printf("[INFO] Instance ID: %s", instance.InstanceId) 267 268 // Store the resulting ID so we can look this up later 269 d.SetId(instance.InstanceId) 270 271 // Wait for the instance to become running so we can get some attributes 272 // that aren't available until later. 273 log.Printf( 274 "[DEBUG] Waiting for instance (%s) to become running", 275 instance.InstanceId) 276 277 stateConf := &resource.StateChangeConf{ 278 Pending: []string{"pending"}, 279 Target: "running", 280 Refresh: InstanceStateRefreshFunc(ec2conn, instance.InstanceId), 281 Timeout: 10 * time.Minute, 282 Delay: 10 * time.Second, 283 MinTimeout: 3 * time.Second, 284 } 285 286 instanceRaw, err := stateConf.WaitForState() 287 if err != nil { 288 return fmt.Errorf( 289 "Error waiting for instance (%s) to become ready: %s", 290 instance.InstanceId, err) 291 } 292 293 instance = instanceRaw.(*ec2.Instance) 294 295 // Initialize the connection info 296 d.SetConnInfo(map[string]string{ 297 "type": "ssh", 298 "host": instance.PublicIpAddress, 299 }) 300 301 // Set our attributes 302 if err := resourceAwsInstanceRead(d, meta); err != nil { 303 return err 304 } 305 306 // Update if we need to 307 return resourceAwsInstanceUpdate(d, meta) 308 } 309 310 func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { 311 ec2conn := meta.(*AWSClient).ec2conn 312 313 resp, err := ec2conn.Instances([]string{d.Id()}, ec2.NewFilter()) 314 if err != nil { 315 // If the instance was not found, return nil so that we can show 316 // that the instance is gone. 317 if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { 318 d.SetId("") 319 return nil 320 } 321 322 // Some other error, report it 323 return err 324 } 325 326 // If nothing was found, then return no state 327 if len(resp.Reservations) == 0 { 328 d.SetId("") 329 return nil 330 } 331 332 instance := &resp.Reservations[0].Instances[0] 333 334 // If the instance is terminated, then it is gone 335 if instance.State.Name == "terminated" { 336 d.SetId("") 337 return nil 338 } 339 340 d.Set("availability_zone", instance.AvailZone) 341 d.Set("key_name", instance.KeyName) 342 d.Set("public_dns", instance.DNSName) 343 d.Set("public_ip", instance.PublicIpAddress) 344 d.Set("private_dns", instance.PrivateDNSName) 345 d.Set("private_ip", instance.PrivateIpAddress) 346 d.Set("subnet_id", instance.SubnetId) 347 d.Set("ebs_optimized", instance.EbsOptimized) 348 d.Set("tags", tagsToMap(instance.Tags)) 349 d.Set("tenancy", instance.Tenancy) 350 351 // Determine whether we're referring to security groups with 352 // IDs or names. We use a heuristic to figure this out. By default, 353 // we use IDs if we're in a VPC. However, if we previously had an 354 // all-name list of security groups, we use names. Or, if we had any 355 // IDs, we use IDs. 356 useID := instance.SubnetId != "" 357 if v := d.Get("security_groups"); v != nil { 358 match := false 359 for _, v := range v.(*schema.Set).List() { 360 if strings.HasPrefix(v.(string), "sg-") { 361 match = true 362 break 363 } 364 } 365 366 useID = match 367 } 368 369 // Build up the security groups 370 sgs := make([]string, len(instance.SecurityGroups)) 371 for i, sg := range instance.SecurityGroups { 372 if useID { 373 sgs[i] = sg.Id 374 } else { 375 sgs[i] = sg.Name 376 } 377 } 378 d.Set("security_groups", sgs) 379 380 volIDs := make([]string, len(instance.BlockDevices)) 381 bdByVolID := make(map[string]ec2.BlockDevice) 382 for i, bd := range instance.BlockDevices { 383 volIDs[i] = bd.VolumeId 384 bdByVolID[bd.VolumeId] = bd 385 } 386 387 volResp, err := ec2conn.Volumes(volIDs, ec2.NewFilter()) 388 if err != nil { 389 return err 390 } 391 392 bds := make([]map[string]interface{}, len(volResp.Volumes)) 393 for i, vol := range volResp.Volumes { 394 volSize, err := strconv.Atoi(vol.Size) 395 if err != nil { 396 return err 397 } 398 bds[i] = make(map[string]interface{}) 399 bds[i]["device_name"] = bdByVolID[vol.VolumeId].DeviceName 400 bds[i]["snapshot_id"] = vol.SnapshotId 401 bds[i]["volume_type"] = vol.VolumeType 402 bds[i]["volume_size"] = volSize 403 bds[i]["delete_on_termination"] = bdByVolID[vol.VolumeId].DeleteOnTermination 404 bds[i]["encrypted"] = vol.Encrypted 405 } 406 d.Set("block_device", bds) 407 408 return nil 409 } 410 411 func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 412 ec2conn := meta.(*AWSClient).ec2conn 413 414 modify := false 415 opts := new(ec2.ModifyInstance) 416 417 if v, ok := d.GetOk("source_dest_check"); ok { 418 opts.SourceDestCheck = v.(bool) 419 opts.SetSourceDestCheck = true 420 modify = true 421 } 422 423 if modify { 424 log.Printf("[INFO] Modifing instance %s: %#v", d.Id(), opts) 425 if _, err := ec2conn.ModifyInstance(d.Id(), opts); err != nil { 426 return err 427 } 428 429 // TODO(mitchellh): wait for the attributes we modified to 430 // persist the change... 431 } 432 433 if err := setTags(ec2conn, d); err != nil { 434 return err 435 } else { 436 d.SetPartial("tags") 437 } 438 439 return nil 440 } 441 442 func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error { 443 ec2conn := meta.(*AWSClient).ec2conn 444 445 log.Printf("[INFO] Terminating instance: %s", d.Id()) 446 if _, err := ec2conn.TerminateInstances([]string{d.Id()}); err != nil { 447 return fmt.Errorf("Error terminating instance: %s", err) 448 } 449 450 log.Printf( 451 "[DEBUG] Waiting for instance (%s) to become terminated", 452 d.Id()) 453 454 stateConf := &resource.StateChangeConf{ 455 Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, 456 Target: "terminated", 457 Refresh: InstanceStateRefreshFunc(ec2conn, d.Id()), 458 Timeout: 10 * time.Minute, 459 Delay: 10 * time.Second, 460 MinTimeout: 3 * time.Second, 461 } 462 463 _, err := stateConf.WaitForState() 464 if err != nil { 465 return fmt.Errorf( 466 "Error waiting for instance (%s) to terminate: %s", 467 d.Id(), err) 468 } 469 470 d.SetId("") 471 return nil 472 } 473 474 // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 475 // an EC2 instance. 476 func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { 477 return func() (interface{}, string, error) { 478 resp, err := conn.Instances([]string{instanceID}, ec2.NewFilter()) 479 if err != nil { 480 if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { 481 // Set this to nil as if we didn't find anything. 482 resp = nil 483 } else { 484 log.Printf("Error on InstanceStateRefresh: %s", err) 485 return nil, "", err 486 } 487 } 488 489 if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { 490 // Sometimes AWS just has consistency issues and doesn't see 491 // our instance yet. Return an empty state. 492 return nil, "", nil 493 } 494 495 i := &resp.Reservations[0].Instances[0] 496 return i, i.State.Name, nil 497 } 498 } 499 500 func resourceAwsInstanceBlockDevicesHash(v interface{}) int { 501 var buf bytes.Buffer 502 m := v.(map[string]interface{}) 503 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 504 buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string))) 505 buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) 506 return hashcode.String(buf.String()) 507 }