github.com/daveadams/terraform@v0.6.4-0.20160830094355-13ce74975936/helper/resource/state.go (about) 1 package resource 2 3 import ( 4 "log" 5 "time" 6 ) 7 8 // StateRefreshFunc is a function type used for StateChangeConf that is 9 // responsible for refreshing the item being watched for a state change. 10 // 11 // It returns three results. `result` is any object that will be returned 12 // as the final object after waiting for state change. This allows you to 13 // return the final updated object, for example an EC2 instance after refreshing 14 // it. 15 // 16 // `state` is the latest state of that object. And `err` is any error that 17 // may have happened while refreshing the state. 18 type StateRefreshFunc func() (result interface{}, state string, err error) 19 20 // StateChangeConf is the configuration struct used for `WaitForState`. 21 type StateChangeConf struct { 22 Delay time.Duration // Wait this time before starting checks 23 Pending []string // States that are "allowed" and will continue trying 24 Refresh StateRefreshFunc // Refreshes the current state 25 Target []string // Target state 26 Timeout time.Duration // The amount of time to wait before timeout 27 MinTimeout time.Duration // Smallest time to wait before refreshes 28 PollInterval time.Duration // Override MinTimeout/backoff and only poll this often 29 NotFoundChecks int // Number of times to allow not found 30 31 // This is to work around inconsistent APIs 32 ContinuousTargetOccurence int // Number of times the Target state has to occur continuously 33 } 34 35 // WaitForState watches an object and waits for it to achieve the state 36 // specified in the configuration using the specified Refresh() func, 37 // waiting the number of seconds specified in the timeout configuration. 38 // 39 // If the Refresh function returns a error, exit immediately with that error. 40 // 41 // If the Refresh function returns a state other than the Target state or one 42 // listed in Pending, return immediately with an error. 43 // 44 // If the Timeout is exceeded before reaching the Target state, return an 45 // error. 46 // 47 // Otherwise, result the result of the first call to the Refresh function to 48 // reach the target state. 49 func (conf *StateChangeConf) WaitForState() (interface{}, error) { 50 log.Printf("[DEBUG] Waiting for state to become: %s", conf.Target) 51 52 notfoundTick := 0 53 targetOccurence := 0 54 55 // Set a default for times to check for not found 56 if conf.NotFoundChecks == 0 { 57 conf.NotFoundChecks = 20 58 } 59 60 if conf.ContinuousTargetOccurence == 0 { 61 conf.ContinuousTargetOccurence = 1 62 } 63 64 var result interface{} 65 var resulterr error 66 var currentState string 67 68 doneCh := make(chan struct{}) 69 go func() { 70 defer close(doneCh) 71 72 // Wait for the delay 73 time.Sleep(conf.Delay) 74 75 wait := 100 * time.Millisecond 76 77 var err error 78 for first := true; ; first = false { 79 if !first { 80 // If a poll interval has been specified, choose that interval. 81 // Otherwise bound the default value. 82 if conf.PollInterval > 0 && conf.PollInterval < 180*time.Second { 83 wait = conf.PollInterval 84 } else { 85 if wait < conf.MinTimeout { 86 wait = conf.MinTimeout 87 } else if wait > 10*time.Second { 88 wait = 10 * time.Second 89 } 90 } 91 92 log.Printf("[TRACE] Waiting %s before next try", wait) 93 time.Sleep(wait) 94 95 // Wait between refreshes using exponential backoff, except when 96 // waiting for the target state to reoccur. 97 if targetOccurence == 0 { 98 wait *= 2 99 } 100 } 101 102 result, currentState, err = conf.Refresh() 103 if err != nil { 104 resulterr = err 105 return 106 } 107 108 // If we're waiting for the absence of a thing, then return 109 if result == nil && len(conf.Target) == 0 { 110 targetOccurence += 1 111 if conf.ContinuousTargetOccurence == targetOccurence { 112 return 113 } else { 114 continue 115 } 116 } 117 118 if result == nil { 119 // If we didn't find the resource, check if we have been 120 // not finding it for awhile, and if so, report an error. 121 notfoundTick += 1 122 if notfoundTick > conf.NotFoundChecks { 123 resulterr = &NotFoundError{ 124 LastError: resulterr, 125 } 126 return 127 } 128 } else { 129 // Reset the counter for when a resource isn't found 130 notfoundTick = 0 131 found := false 132 133 for _, allowed := range conf.Target { 134 if currentState == allowed { 135 found = true 136 targetOccurence += 1 137 if conf.ContinuousTargetOccurence == targetOccurence { 138 return 139 } else { 140 continue 141 } 142 } 143 } 144 145 for _, allowed := range conf.Pending { 146 if currentState == allowed { 147 found = true 148 targetOccurence = 0 149 break 150 } 151 } 152 153 if !found { 154 resulterr = &UnexpectedStateError{ 155 LastError: resulterr, 156 State: currentState, 157 ExpectedState: conf.Target, 158 } 159 return 160 } 161 } 162 } 163 }() 164 165 select { 166 case <-doneCh: 167 return result, resulterr 168 case <-time.After(conf.Timeout): 169 return nil, &TimeoutError{ 170 LastError: resulterr, 171 LastState: currentState, 172 ExpectedState: conf.Target, 173 } 174 } 175 }