github.com/aavshr/aws-sdk-go@v1.41.3/aws/request/waiter.go (about) 1 package request 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/aavshr/aws-sdk-go/aws" 8 "github.com/aavshr/aws-sdk-go/aws/awserr" 9 "github.com/aavshr/aws-sdk-go/aws/awsutil" 10 ) 11 12 // WaiterResourceNotReadyErrorCode is the error code returned by a waiter when 13 // the waiter's max attempts have been exhausted. 14 const WaiterResourceNotReadyErrorCode = "ResourceNotReady" 15 16 // A WaiterOption is a function that will update the Waiter value's fields to 17 // configure the waiter. 18 type WaiterOption func(*Waiter) 19 20 // WithWaiterMaxAttempts returns the maximum number of times the waiter should 21 // attempt to check the resource for the target state. 22 func WithWaiterMaxAttempts(max int) WaiterOption { 23 return func(w *Waiter) { 24 w.MaxAttempts = max 25 } 26 } 27 28 // WaiterDelay will return a delay the waiter should pause between attempts to 29 // check the resource state. The passed in attempt is the number of times the 30 // Waiter has checked the resource state. 31 // 32 // Attempt is the number of attempts the Waiter has made checking the resource 33 // state. 34 type WaiterDelay func(attempt int) time.Duration 35 36 // ConstantWaiterDelay returns a WaiterDelay that will always return a constant 37 // delay the waiter should use between attempts. It ignores the number of 38 // attempts made. 39 func ConstantWaiterDelay(delay time.Duration) WaiterDelay { 40 return func(attempt int) time.Duration { 41 return delay 42 } 43 } 44 45 // WithWaiterDelay will set the Waiter to use the WaiterDelay passed in. 46 func WithWaiterDelay(delayer WaiterDelay) WaiterOption { 47 return func(w *Waiter) { 48 w.Delay = delayer 49 } 50 } 51 52 // WithWaiterLogger returns a waiter option to set the logger a waiter 53 // should use to log warnings and errors to. 54 func WithWaiterLogger(logger aws.Logger) WaiterOption { 55 return func(w *Waiter) { 56 w.Logger = logger 57 } 58 } 59 60 // WithWaiterRequestOptions returns a waiter option setting the request 61 // options for each request the waiter makes. Appends to waiter's request 62 // options already set. 63 func WithWaiterRequestOptions(opts ...Option) WaiterOption { 64 return func(w *Waiter) { 65 w.RequestOptions = append(w.RequestOptions, opts...) 66 } 67 } 68 69 // A Waiter provides the functionality to perform a blocking call which will 70 // wait for a resource state to be satisfied by a service. 71 // 72 // This type should not be used directly. The API operations provided in the 73 // service packages prefixed with "WaitUntil" should be used instead. 74 type Waiter struct { 75 Name string 76 Acceptors []WaiterAcceptor 77 Logger aws.Logger 78 79 MaxAttempts int 80 Delay WaiterDelay 81 82 RequestOptions []Option 83 NewRequest func([]Option) (*Request, error) 84 SleepWithContext func(aws.Context, time.Duration) error 85 } 86 87 // ApplyOptions updates the waiter with the list of waiter options provided. 88 func (w *Waiter) ApplyOptions(opts ...WaiterOption) { 89 for _, fn := range opts { 90 fn(w) 91 } 92 } 93 94 // WaiterState are states the waiter uses based on WaiterAcceptor definitions 95 // to identify if the resource state the waiter is waiting on has occurred. 96 type WaiterState int 97 98 // String returns the string representation of the waiter state. 99 func (s WaiterState) String() string { 100 switch s { 101 case SuccessWaiterState: 102 return "success" 103 case FailureWaiterState: 104 return "failure" 105 case RetryWaiterState: 106 return "retry" 107 default: 108 return "unknown waiter state" 109 } 110 } 111 112 // States the waiter acceptors will use to identify target resource states. 113 const ( 114 SuccessWaiterState WaiterState = iota // waiter successful 115 FailureWaiterState // waiter failed 116 RetryWaiterState // waiter needs to be retried 117 ) 118 119 // WaiterMatchMode is the mode that the waiter will use to match the WaiterAcceptor 120 // definition's Expected attribute. 121 type WaiterMatchMode int 122 123 // Modes the waiter will use when inspecting API response to identify target 124 // resource states. 125 const ( 126 PathAllWaiterMatch WaiterMatchMode = iota // match on all paths 127 PathWaiterMatch // match on specific path 128 PathAnyWaiterMatch // match on any path 129 PathListWaiterMatch // match on list of paths 130 StatusWaiterMatch // match on status code 131 ErrorWaiterMatch // match on error 132 ) 133 134 // String returns the string representation of the waiter match mode. 135 func (m WaiterMatchMode) String() string { 136 switch m { 137 case PathAllWaiterMatch: 138 return "pathAll" 139 case PathWaiterMatch: 140 return "path" 141 case PathAnyWaiterMatch: 142 return "pathAny" 143 case PathListWaiterMatch: 144 return "pathList" 145 case StatusWaiterMatch: 146 return "status" 147 case ErrorWaiterMatch: 148 return "error" 149 default: 150 return "unknown waiter match mode" 151 } 152 } 153 154 // WaitWithContext will make requests for the API operation using NewRequest to 155 // build API requests. The request's response will be compared against the 156 // Waiter's Acceptors to determine the successful state of the resource the 157 // waiter is inspecting. 158 // 159 // The passed in context must not be nil. If it is nil a panic will occur. The 160 // Context will be used to cancel the waiter's pending requests and retry delays. 161 // Use aws.BackgroundContext if no context is available. 162 // 163 // The waiter will continue until the target state defined by the Acceptors, 164 // or the max attempts expires. 165 // 166 // Will return the WaiterResourceNotReadyErrorCode error code if the waiter's 167 // retryer ShouldRetry returns false. This normally will happen when the max 168 // wait attempts expires. 169 func (w Waiter) WaitWithContext(ctx aws.Context) error { 170 171 for attempt := 1; ; attempt++ { 172 req, err := w.NewRequest(w.RequestOptions) 173 if err != nil { 174 waiterLogf(w.Logger, "unable to create request %v", err) 175 return err 176 } 177 req.Handlers.Build.PushBack(MakeAddToUserAgentFreeFormHandler("Waiter")) 178 err = req.Send() 179 180 // See if any of the acceptors match the request's response, or error 181 for _, a := range w.Acceptors { 182 if matched, matchErr := a.match(w.Name, w.Logger, req, err); matched { 183 return matchErr 184 } 185 } 186 187 // The Waiter should only check the resource state MaxAttempts times 188 // This is here instead of in the for loop above to prevent delaying 189 // unnecessary when the waiter will not retry. 190 if attempt == w.MaxAttempts { 191 break 192 } 193 194 // Delay to wait before inspecting the resource again 195 delay := w.Delay(attempt) 196 if sleepFn := req.Config.SleepDelay; sleepFn != nil { 197 // Support SleepDelay for backwards compatibility and testing 198 sleepFn(delay) 199 } else { 200 sleepCtxFn := w.SleepWithContext 201 if sleepCtxFn == nil { 202 sleepCtxFn = aws.SleepWithContext 203 } 204 205 if err := sleepCtxFn(ctx, delay); err != nil { 206 return awserr.New(CanceledErrorCode, "waiter context canceled", err) 207 } 208 } 209 } 210 211 return awserr.New(WaiterResourceNotReadyErrorCode, "exceeded wait attempts", nil) 212 } 213 214 // A WaiterAcceptor provides the information needed to wait for an API operation 215 // to complete. 216 type WaiterAcceptor struct { 217 State WaiterState 218 Matcher WaiterMatchMode 219 Argument string 220 Expected interface{} 221 } 222 223 // match returns if the acceptor found a match with the passed in request 224 // or error. True is returned if the acceptor made a match, error is returned 225 // if there was an error attempting to perform the match. 226 func (a *WaiterAcceptor) match(name string, l aws.Logger, req *Request, err error) (bool, error) { 227 result := false 228 var vals []interface{} 229 230 switch a.Matcher { 231 case PathAllWaiterMatch, PathWaiterMatch: 232 // Require all matches to be equal for result to match 233 vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument) 234 if len(vals) == 0 { 235 break 236 } 237 result = true 238 for _, val := range vals { 239 if !awsutil.DeepEqual(val, a.Expected) { 240 result = false 241 break 242 } 243 } 244 case PathAnyWaiterMatch: 245 // Only a single match needs to equal for the result to match 246 vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument) 247 for _, val := range vals { 248 if awsutil.DeepEqual(val, a.Expected) { 249 result = true 250 break 251 } 252 } 253 case PathListWaiterMatch: 254 // ignored matcher 255 case StatusWaiterMatch: 256 s := a.Expected.(int) 257 result = s == req.HTTPResponse.StatusCode 258 case ErrorWaiterMatch: 259 if aerr, ok := err.(awserr.Error); ok { 260 result = aerr.Code() == a.Expected.(string) 261 } 262 default: 263 waiterLogf(l, "WARNING: Waiter %s encountered unexpected matcher: %s", 264 name, a.Matcher) 265 } 266 267 if !result { 268 // If there was no matching result found there is nothing more to do 269 // for this response, retry the request. 270 return false, nil 271 } 272 273 switch a.State { 274 case SuccessWaiterState: 275 // waiter completed 276 return true, nil 277 case FailureWaiterState: 278 // Waiter failure state triggered 279 return true, awserr.New(WaiterResourceNotReadyErrorCode, 280 "failed waiting for successful resource state", err) 281 case RetryWaiterState: 282 // clear the error and retry the operation 283 return false, nil 284 default: 285 waiterLogf(l, "WARNING: Waiter %s encountered unexpected state: %s", 286 name, a.State) 287 return false, nil 288 } 289 } 290 291 func waiterLogf(logger aws.Logger, msg string, args ...interface{}) { 292 if logger != nil { 293 logger.Log(fmt.Sprintf(msg, args...)) 294 } 295 }