github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/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 // SpotRequestStateRefreshFunc returns a StateRefreshFunc that is used to watch 85 // a spot request for state changes. 86 func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefreshFunc { 87 return func() (interface{}, string, error) { 88 resp, err := conn.DescribeSpotRequests([]string{spotRequestId}, ec2.NewFilter()) 89 if err != nil { 90 if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidSpotInstanceRequestID.NotFound" { 91 // Set this to nil as if we didn't find anything. 92 resp = nil 93 } else { 94 log.Printf("Error on SpotRequestStateRefresh: %s", err) 95 return nil, "", err 96 } 97 } 98 99 if resp == nil || len(resp.SpotRequestResults) == 0 { 100 // Sometimes AWS has consistency issues and doesn't see the 101 // SpotRequest. Return an empty state. 102 return nil, "", nil 103 } 104 105 i := resp.SpotRequestResults[0] 106 return i, i.State, nil 107 } 108 } 109 110 // WaitForState watches an object and waits for it to achieve a certain 111 // state. 112 func WaitForState(conf *StateChangeConf) (i interface{}, err error) { 113 log.Printf("Waiting for state to become: %s", conf.Target) 114 115 notfoundTick := 0 116 117 for { 118 var currentState string 119 i, currentState, err = conf.Refresh() 120 if err != nil { 121 return 122 } 123 124 if i == nil { 125 // If we didn't find the resource, check if we have been 126 // not finding it for awhile, and if so, report an error. 127 notfoundTick += 1 128 if notfoundTick > 20 { 129 return nil, errors.New("couldn't find resource") 130 } 131 } else { 132 // Reset the counter for when a resource isn't found 133 notfoundTick = 0 134 135 if currentState == conf.Target { 136 return 137 } 138 139 if conf.StepState != nil { 140 if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok { 141 return nil, errors.New("interrupted") 142 } 143 } 144 145 found := false 146 for _, allowed := range conf.Pending { 147 if currentState == allowed { 148 found = true 149 break 150 } 151 } 152 153 if !found { 154 err := fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target) 155 return nil, err 156 } 157 } 158 159 time.Sleep(2 * time.Second) 160 } 161 162 return 163 }