instance.go 3.25 KB
Newer Older
1
package common
2 3

import (
4
	"errors"
5 6
	"fmt"
	"github.com/mitchellh/goamz/ec2"
7
	"github.com/mitchellh/multistep"
8 9 10 11
	"log"
	"time"
)

12 13 14 15 16 17 18 19 20 21 22 23 24
// 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`.
25 26 27
type StateChangeConf struct {
	Conn      *ec2.EC2
	Pending   []string
28
	Refresh   StateRefreshFunc
29
	StepState multistep.StateBag
30 31 32
	Target    string
}

33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
// AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an AMI for state changes.
func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc {
	return func() (interface{}, string, error) {
		resp, err := conn.Images([]string{imageId}, ec2.NewFilter())
		if err != nil {
			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
				// Set this to nil as if we didn't find anything.
				resp = nil
			} else {
				log.Printf("Error on AMIStateRefresh: %s", err)
				return nil, "", err
			}
		}

		if resp == nil || len(resp.Images) == 0 {
			// Sometimes AWS has consistency issues and doesn't see the
			// AMI. Return an empty state.
			return nil, "", nil
		}

		i := resp.Images[0]
		return i, i.State, nil
	}
}

59 60 61
// InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an EC2 instance.
func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
62 63 64
	return func() (interface{}, string, error) {
		resp, err := conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
		if err != nil {
65
			log.Printf("Error on InstanceStateRefresh: %s", err)
66 67 68
			return nil, "", err
		}

69 70 71 72 73 74
		if 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
		}

75 76 77 78 79
		i = &resp.Reservations[0].Instances[0]
		return i, i.State.Name, nil
	}
}

80 81
// WaitForState watches an object and waits for it to achieve a certain
// state.
82
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
83
	log.Printf("Waiting for state to become: %s", conf.Target)
84

85 86 87 88 89 90 91
	for {
		var currentState string
		i, currentState, err = conf.Refresh()
		if err != nil {
			return
		}

92 93 94 95 96 97
		// Check states only if we were able to refresh to an instance
		// that exists.
		if i != nil {
			if currentState == conf.Target {
				return
			}
98

99 100 101 102
			if conf.StepState != nil {
				if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
					return nil, errors.New("interrupted")
				}
103
			}
104

105 106 107 108 109 110
			found := false
			for _, allowed := range conf.Pending {
				if currentState == allowed {
					found = true
					break
				}
111 112
			}

113 114 115 116
			if !found {
				fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
				return
			}
117 118
		}

119 120 121 122 123
		time.Sleep(2 * time.Second)
	}

	return
}