github.com/supr/packer@v0.3.10-0.20131015195147-7b09e24ac3c1/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 Conn *ec2.EC2 27 Pending []string 28 Refresh StateRefreshFunc 29 StepState multistep.StateBag 30 Target string 31 } 32 33 // AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch 34 // an AMI for state changes. 35 func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc { 36 return func() (interface{}, string, error) { 37 resp, err := conn.Images([]string{imageId}, ec2.NewFilter()) 38 if err != nil { 39 if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" { 40 // Set this to nil as if we didn't find anything. 41 resp = nil 42 } else { 43 log.Printf("Error on AMIStateRefresh: %s", err) 44 return nil, "", err 45 } 46 } 47 48 if resp == nil || len(resp.Images) == 0 { 49 // Sometimes AWS has consistency issues and doesn't see the 50 // AMI. Return an empty state. 51 return nil, "", nil 52 } 53 54 i := resp.Images[0] 55 return i, i.State, nil 56 } 57 } 58 59 // InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch 60 // an EC2 instance. 61 func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc { 62 return func() (interface{}, string, error) { 63 resp, err := conn.Instances([]string{i.InstanceId}, ec2.NewFilter()) 64 if err != nil { 65 if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { 66 // Set this to nil as if we didn't find anything. 67 resp = nil 68 } else { 69 log.Printf("Error on InstanceStateRefresh: %s", err) 70 return nil, "", err 71 } 72 } 73 74 if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { 75 // Sometimes AWS just has consistency issues and doesn't see 76 // our instance yet. Return an empty state. 77 return nil, "", nil 78 } 79 80 i = &resp.Reservations[0].Instances[0] 81 return i, i.State.Name, nil 82 } 83 } 84 85 // WaitForState watches an object and waits for it to achieve a certain 86 // state. 87 func WaitForState(conf *StateChangeConf) (i interface{}, err error) { 88 log.Printf("Waiting for state to become: %s", conf.Target) 89 90 notfoundTick := 0 91 92 for { 93 var currentState string 94 i, currentState, err = conf.Refresh() 95 if err != nil { 96 return 97 } 98 99 if i == nil { 100 // If we didn't find the resource, check if we have been 101 // not finding it for awhile, and if so, report an error. 102 notfoundTick += 1 103 if notfoundTick > 20 { 104 return nil, errors.New("couldn't find resource") 105 } 106 } else { 107 // Reset the counter for when a resource isn't found 108 notfoundTick = 0 109 110 if currentState == conf.Target { 111 return 112 } 113 114 if conf.StepState != nil { 115 if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok { 116 return nil, errors.New("interrupted") 117 } 118 } 119 120 found := false 121 for _, allowed := range conf.Pending { 122 if currentState == allowed { 123 found = true 124 break 125 } 126 } 127 128 if !found { 129 fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target) 130 return 131 } 132 } 133 134 time.Sleep(2 * time.Second) 135 } 136 137 return 138 }