github.com/jbronn/packer@v0.1.6-0.20140120165540-8a1364dbd817/builder/amazon/common/state.go (about) 1 package common 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/mitchellh/goamz/ec2" 7 "github.com/mitchellh/multistep" 8 "log" 9 "time" 10 ) 11 12 // StateRefreshFunc is a function type used for StateChangeConf that is 13 // responsible for refreshing the item being watched for a state change. 14 // 15 // It returns three results. `result` is any object that will be returned 16 // as the final object after waiting for state change. This allows you to 17 // return the final updated object, for example an EC2 instance after refreshing 18 // it. 19 // 20 // `state` is the latest state of that object. And `err` is any error that 21 // may have happened while refreshing the state. 22 type StateRefreshFunc func() (result interface{}, state string, err error) 23 24 // StateChangeConf is the configuration struct used for `WaitForState`. 25 type StateChangeConf struct { 26 Pending []string 27 Refresh StateRefreshFunc 28 StepState multistep.StateBag 29 Target string 30 } 31 32 // AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch 33 // an AMI for state changes. 34 func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc { 35 return func() (interface{}, string, error) { 36 resp, err := conn.Images([]string{imageId}, ec2.NewFilter()) 37 if err != nil { 38 if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" { 39 // Set this to nil as if we didn't find anything. 40 resp = nil 41 } else { 42 log.Printf("Error on AMIStateRefresh: %s", err) 43 return nil, "", err 44 } 45 } 46 47 if resp == nil || len(resp.Images) == 0 { 48 // Sometimes AWS has consistency issues and doesn't see the 49 // AMI. Return an empty state. 50 return nil, "", nil 51 } 52 53 i := resp.Images[0] 54 return i, i.State, nil 55 } 56 } 57 58 // InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch 59 // an EC2 instance. 60 func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc { 61 return func() (interface{}, string, error) { 62 resp, err := conn.Instances([]string{i.InstanceId}, ec2.NewFilter()) 63 if err != nil { 64 if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { 65 // Set this to nil as if we didn't find anything. 66 resp = nil 67 } else { 68 log.Printf("Error on InstanceStateRefresh: %s", err) 69 return nil, "", err 70 } 71 } 72 73 if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { 74 // Sometimes AWS just has consistency issues and doesn't see 75 // our instance yet. Return an empty state. 76 return nil, "", nil 77 } 78 79 i = &resp.Reservations[0].Instances[0] 80 return i, i.State.Name, nil 81 } 82 } 83 84 // WaitForState watches an object and waits for it to achieve a certain 85 // state. 86 func WaitForState(conf *StateChangeConf) (i interface{}, err error) { 87 log.Printf("Waiting for state to become: %s", conf.Target) 88 89 notfoundTick := 0 90 91 for { 92 var currentState string 93 i, currentState, err = conf.Refresh() 94 if err != nil { 95 return 96 } 97 98 if i == nil { 99 // If we didn't find the resource, check if we have been 100 // not finding it for awhile, and if so, report an error. 101 notfoundTick += 1 102 if notfoundTick > 20 { 103 return nil, errors.New("couldn't find resource") 104 } 105 } else { 106 // Reset the counter for when a resource isn't found 107 notfoundTick = 0 108 109 if currentState == conf.Target { 110 return 111 } 112 113 if conf.StepState != nil { 114 if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok { 115 return nil, errors.New("interrupted") 116 } 117 } 118 119 found := false 120 for _, allowed := range conf.Pending { 121 if currentState == allowed { 122 found = true 123 break 124 } 125 } 126 127 if !found { 128 fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target) 129 return 130 } 131 } 132 133 time.Sleep(2 * time.Second) 134 } 135 136 return 137 }