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