diff --git a/builtin/providers/aws/resources.go b/builtin/providers/aws/resources.go index d5530e1ac..4881409df 100644 --- a/builtin/providers/aws/resources.go +++ b/builtin/providers/aws/resources.go @@ -34,19 +34,39 @@ func resource_aws_instance_create( InstanceType: d.Attributes["instance_type"].New, } - log.Printf("Run configuration: %#v", runOpts) + log.Printf("[DEBUG] Run configuration: %#v", runOpts) runResp, err := ec2conn.RunInstances(runOpts) if err != nil { return nil, fmt.Errorf("Error launching source instance: %s", err) } instance := &runResp.Instances[0] - log.Printf("Instance ID: %s", instance.InstanceId) + log.Printf("[INFO] Instance ID: %s", instance.InstanceId) - // TODO(mitchellh): wait until running - - rs := s.MergeDiff(d) + // Store the resource state now so that we can return it in the case + // of any errors. + rs := new(terraform.ResourceState) rs.ID = instance.InstanceId + + // Wait for the instance to become running so we can get some attributes + // that aren't available until later. + log.Printf( + "[DEBUG] Waiting for instance (%s) to become running", + instance.InstanceId) + instanceRaw, err := WaitForState(&StateChangeConf{ + Pending: []string{"pending"}, + Target: "running", + Refresh: InstanceStateRefreshFunc(ec2conn, instance), + }) + if err != nil { + return rs, fmt.Errorf( + "Error waiting for instance (%s) to become ready: %s", + instance.InstanceId, err) + } + instance = instanceRaw.(*ec2.Instance) + + // Set our attributes + rs = rs.MergeDiff(d) rs.Attributes["public_dns"] = instance.DNSName rs.Attributes["public_ip"] = instance.PublicIpAddress rs.Attributes["private_dns"] = instance.PrivateDNSName diff --git a/builtin/providers/aws/state.go b/builtin/providers/aws/state.go new file mode 100644 index 000000000..7b0363d8b --- /dev/null +++ b/builtin/providers/aws/state.go @@ -0,0 +1,104 @@ +package aws + +import ( + "errors" + "fmt" + "log" + "time" + + "github.com/mitchellh/goamz/ec2" +) + +// StateRefreshFunc is a function type used for StateChangeConf that is +// responsible for refreshing the item being watched for a state change. +// +// It returns three results. `result` is any object that will be returned +// as the final object after waiting for state change. This allows you to +// return the final updated object, for example an EC2 instance after refreshing +// it. +// +// `state` is the latest state of that object. And `err` is any error that +// may have happened while refreshing the state. +type StateRefreshFunc func() (result interface{}, state string, err error) + +// StateChangeConf is the configuration struct used for `WaitForState`. +type StateChangeConf struct { + Pending []string + Refresh StateRefreshFunc + Target string +} + +// InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch +// an EC2 instance. +func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.Instances([]string{i.InstanceId}, ec2.NewFilter()) + if err != nil { + if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { + // Set this to nil as if we didn't find anything. + resp = nil + } else { + log.Printf("Error on InstanceStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + i = &resp.Reservations[0].Instances[0] + return i, i.State.Name, nil + } +} + +// WaitForState watches an object and waits for it to achieve a certain +// state. +func WaitForState(conf *StateChangeConf) (i interface{}, err error) { + log.Printf("Waiting for state to become: %s", conf.Target) + + notfoundTick := 0 + + for { + var currentState string + i, currentState, err = conf.Refresh() + if err != nil { + return + } + + if i == nil { + // If we didn't find the resource, check if we have been + // not finding it for awhile, and if so, report an error. + notfoundTick += 1 + if notfoundTick > 20 { + return nil, errors.New("couldn't find resource") + } + } else { + // Reset the counter for when a resource isn't found + notfoundTick = 0 + + if currentState == conf.Target { + return + } + + found := false + for _, allowed := range conf.Pending { + if currentState == allowed { + found = true + break + } + } + + if !found { + fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target) + return + } + } + + time.Sleep(2 * time.Second) + } + + return +}