github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/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 "strconv" 9 "strings" 10 "time" 11 12 "github.com/hashicorp/terraform/flatmap" 13 "github.com/hashicorp/terraform/helper/diff" 14 "github.com/hashicorp/terraform/helper/resource" 15 "github.com/hashicorp/terraform/terraform" 16 "github.com/mitchellh/goamz/ec2" 17 ) 18 19 func resource_aws_instance_create( 20 s *terraform.ResourceState, 21 d *terraform.ResourceDiff, 22 meta interface{}) (*terraform.ResourceState, error) { 23 p := meta.(*ResourceProvider) 24 ec2conn := p.ec2conn 25 26 // Merge the diff into the state so that we have all the attributes 27 // properly. 28 rs := s.MergeDiff(d) 29 delete(rs.Attributes, "source_dest_check") 30 31 // Figure out user data 32 userData := "" 33 if attr, ok := d.Attributes["user_data"]; ok { 34 userData = attr.NewExtra.(string) 35 } 36 37 // Build the creation struct 38 runOpts := &ec2.RunInstances{ 39 ImageId: rs.Attributes["ami"], 40 InstanceType: rs.Attributes["instance_type"], 41 KeyName: rs.Attributes["key_name"], 42 SubnetId: rs.Attributes["subnet_id"], 43 UserData: []byte(userData), 44 } 45 if raw := flatmap.Expand(rs.Attributes, "security_groups"); raw != nil { 46 if sgs, ok := raw.([]interface{}); ok { 47 for _, sg := range sgs { 48 str, ok := sg.(string) 49 if !ok { 50 continue 51 } 52 53 var g ec2.SecurityGroup 54 if runOpts.SubnetId != "" { 55 g.Id = str 56 } else { 57 g.Name = str 58 } 59 60 runOpts.SecurityGroups = append(runOpts.SecurityGroups, g) 61 } 62 } 63 } 64 65 // Create the instance 66 log.Printf("[DEBUG] Run configuration: %#v", runOpts) 67 runResp, err := ec2conn.RunInstances(runOpts) 68 if err != nil { 69 return nil, fmt.Errorf("Error launching source instance: %s", err) 70 } 71 72 instance := &runResp.Instances[0] 73 log.Printf("[INFO] Instance ID: %s", instance.InstanceId) 74 75 // Store the resulting ID so we can look this up later 76 rs.ID = instance.InstanceId 77 78 // Wait for the instance to become running so we can get some attributes 79 // that aren't available until later. 80 log.Printf( 81 "[DEBUG] Waiting for instance (%s) to become running", 82 instance.InstanceId) 83 84 stateConf := &resource.StateChangeConf{ 85 Pending: []string{"pending"}, 86 Target: "running", 87 Refresh: InstanceStateRefreshFunc(ec2conn, instance.InstanceId), 88 Timeout: 10 * time.Minute, 89 Delay: 10 * time.Second, 90 MinTimeout: 3 * time.Second, 91 } 92 93 instanceRaw, err := stateConf.WaitForState() 94 95 if err != nil { 96 return rs, fmt.Errorf( 97 "Error waiting for instance (%s) to become ready: %s", 98 instance.InstanceId, err) 99 } 100 101 instance = instanceRaw.(*ec2.Instance) 102 103 // Initialize the connection info 104 rs.ConnInfo["type"] = "ssh" 105 rs.ConnInfo["host"] = instance.PublicIpAddress 106 107 // Set our attributes 108 rs, err = resource_aws_instance_update_state(rs, instance) 109 if err != nil { 110 return rs, err 111 } 112 113 // Update if we need to 114 return resource_aws_instance_update(rs, d, meta) 115 } 116 117 func resource_aws_instance_update( 118 s *terraform.ResourceState, 119 d *terraform.ResourceDiff, 120 meta interface{}) (*terraform.ResourceState, error) { 121 p := meta.(*ResourceProvider) 122 ec2conn := p.ec2conn 123 rs := s.MergeDiff(d) 124 125 modify := false 126 opts := new(ec2.ModifyInstance) 127 128 if attr, ok := d.Attributes["source_dest_check"]; ok { 129 modify = true 130 opts.SourceDestCheck = attr.New != "" && attr.New != "false" 131 opts.SetSourceDestCheck = true 132 rs.Attributes["source_dest_check"] = strconv.FormatBool( 133 opts.SourceDestCheck) 134 } 135 136 if modify { 137 log.Printf("[INFO] Modifing instance %s: %#v", s.ID, opts) 138 if _, err := ec2conn.ModifyInstance(s.ID, opts); err != nil { 139 return s, err 140 } 141 142 // TODO(mitchellh): wait for the attributes we modified to 143 // persist the change... 144 } 145 146 return rs, nil 147 } 148 149 func resource_aws_instance_destroy( 150 s *terraform.ResourceState, 151 meta interface{}) error { 152 p := meta.(*ResourceProvider) 153 ec2conn := p.ec2conn 154 155 log.Printf("[INFO] Terminating instance: %s", s.ID) 156 if _, err := ec2conn.TerminateInstances([]string{s.ID}); err != nil { 157 return fmt.Errorf("Error terminating instance: %s", err) 158 } 159 160 log.Printf( 161 "[DEBUG] Waiting for instance (%s) to become terminated", 162 s.ID) 163 164 stateConf := &resource.StateChangeConf{ 165 Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, 166 Target: "terminated", 167 Refresh: InstanceStateRefreshFunc(ec2conn, s.ID), 168 Timeout: 10 * time.Minute, 169 Delay: 10 * time.Second, 170 MinTimeout: 3 * time.Second, 171 } 172 173 _, err := stateConf.WaitForState() 174 175 if err != nil { 176 return fmt.Errorf( 177 "Error waiting for instance (%s) to terminate: %s", 178 s.ID, err) 179 } 180 181 return nil 182 } 183 184 func resource_aws_instance_diff( 185 s *terraform.ResourceState, 186 c *terraform.ResourceConfig, 187 meta interface{}) (*terraform.ResourceDiff, error) { 188 b := &diff.ResourceBuilder{ 189 Attrs: map[string]diff.AttrType{ 190 "ami": diff.AttrTypeCreate, 191 "availability_zone": diff.AttrTypeCreate, 192 "instance_type": diff.AttrTypeCreate, 193 "key_name": diff.AttrTypeCreate, 194 "security_groups": diff.AttrTypeCreate, 195 "subnet_id": diff.AttrTypeCreate, 196 "source_dest_check": diff.AttrTypeUpdate, 197 "user_data": diff.AttrTypeCreate, 198 }, 199 200 ComputedAttrs: []string{ 201 "availability_zone", 202 "key_name", 203 "public_dns", 204 "public_ip", 205 "private_dns", 206 "private_ip", 207 "security_groups", 208 "subnet_id", 209 }, 210 211 PreProcess: map[string]diff.PreProcessFunc{ 212 "user_data": func(v string) string { 213 hash := sha1.Sum([]byte(v)) 214 return hex.EncodeToString(hash[:]) 215 }, 216 }, 217 } 218 219 return b.Diff(s, c) 220 } 221 222 func resource_aws_instance_refresh( 223 s *terraform.ResourceState, 224 meta interface{}) (*terraform.ResourceState, error) { 225 p := meta.(*ResourceProvider) 226 ec2conn := p.ec2conn 227 228 resp, err := ec2conn.Instances([]string{s.ID}, ec2.NewFilter()) 229 if err != nil { 230 // If the instance was not found, return nil so that we can show 231 // that the instance is gone. 232 if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { 233 return nil, nil 234 } 235 236 // Some other error, report it 237 return s, err 238 } 239 240 // If nothing was found, then return no state 241 if len(resp.Reservations) == 0 { 242 return nil, nil 243 } 244 245 instance := &resp.Reservations[0].Instances[0] 246 247 // If the instance is terminated, then it is gone 248 if instance.State.Name == "terminated" { 249 return nil, nil 250 } 251 252 return resource_aws_instance_update_state(s, instance) 253 } 254 255 func resource_aws_instance_update_state( 256 s *terraform.ResourceState, 257 instance *ec2.Instance) (*terraform.ResourceState, error) { 258 s.Attributes["availability_zone"] = instance.AvailZone 259 s.Attributes["key_name"] = instance.KeyName 260 s.Attributes["public_dns"] = instance.DNSName 261 s.Attributes["public_ip"] = instance.PublicIpAddress 262 s.Attributes["private_dns"] = instance.PrivateDNSName 263 s.Attributes["private_ip"] = instance.PrivateIpAddress 264 s.Attributes["subnet_id"] = instance.SubnetId 265 s.Dependencies = nil 266 267 // Extract the existing security groups 268 useID := false 269 if raw := flatmap.Expand(s.Attributes, "security_groups"); raw != nil { 270 if sgs, ok := raw.([]interface{}); ok { 271 for _, sg := range sgs { 272 str, ok := sg.(string) 273 if !ok { 274 continue 275 } 276 277 if strings.HasPrefix(str, "sg-") { 278 useID = true 279 break 280 } 281 } 282 } 283 } 284 285 // Build up the security groups 286 sgs := make([]string, len(instance.SecurityGroups)) 287 for i, sg := range instance.SecurityGroups { 288 if instance.SubnetId != "" && useID { 289 sgs[i] = sg.Id 290 } else { 291 sgs[i] = sg.Name 292 } 293 294 s.Dependencies = append(s.Dependencies, 295 terraform.ResourceDependency{ID: sg.Id}, 296 ) 297 } 298 flatmap.Map(s.Attributes).Merge(flatmap.Flatten(map[string]interface{}{ 299 "security_groups": sgs, 300 })) 301 302 if instance.SubnetId != "" { 303 s.Dependencies = append(s.Dependencies, 304 terraform.ResourceDependency{ID: instance.SubnetId}, 305 ) 306 } 307 308 return s, nil 309 } 310 311 // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 312 // an EC2 instance. 313 func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { 314 return func() (interface{}, string, error) { 315 resp, err := conn.Instances([]string{instanceID}, ec2.NewFilter()) 316 if err != nil { 317 if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { 318 // Set this to nil as if we didn't find anything. 319 resp = nil 320 } else { 321 log.Printf("Error on InstanceStateRefresh: %s", err) 322 return nil, "", err 323 } 324 } 325 326 if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { 327 // Sometimes AWS just has consistency issues and doesn't see 328 // our instance yet. Return an empty state. 329 return nil, "", nil 330 } 331 332 i := &resp.Reservations[0].Instances[0] 333 return i, i.State.Name, nil 334 } 335 }