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