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