]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/aws/aws-sdk-go/aws/request/waiter.go
Merge branch 'fix_read_test' of github.com:alexandreFre/terraform-provider-statuscake
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / aws / aws-sdk-go / aws / request / waiter.go
1 package request
2
3 import (
4 "fmt"
5 "time"
6
7 "github.com/aws/aws-sdk-go/aws"
8 "github.com/aws/aws-sdk-go/aws/awserr"
9 "github.com/aws/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 }