]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/aws/aws-sdk-go/aws/request/waiter.go
Initial transfer of provider code
[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 }
85
86 // ApplyOptions updates the waiter with the list of waiter options provided.
87 func (w *Waiter) ApplyOptions(opts ...WaiterOption) {
88 for _, fn := range opts {
89 fn(w)
90 }
91 }
92
93 // WaiterState are states the waiter uses based on WaiterAcceptor definitions
94 // to identify if the resource state the waiter is waiting on has occurred.
95 type WaiterState int
96
97 // String returns the string representation of the waiter state.
98 func (s WaiterState) String() string {
99 switch s {
100 case SuccessWaiterState:
101 return "success"
102 case FailureWaiterState:
103 return "failure"
104 case RetryWaiterState:
105 return "retry"
106 default:
107 return "unknown waiter state"
108 }
109 }
110
111 // States the waiter acceptors will use to identify target resource states.
112 const (
113 SuccessWaiterState WaiterState = iota // waiter successful
114 FailureWaiterState // waiter failed
115 RetryWaiterState // waiter needs to be retried
116 )
117
118 // WaiterMatchMode is the mode that the waiter will use to match the WaiterAcceptor
119 // definition's Expected attribute.
120 type WaiterMatchMode int
121
122 // Modes the waiter will use when inspecting API response to identify target
123 // resource states.
124 const (
125 PathAllWaiterMatch WaiterMatchMode = iota // match on all paths
126 PathWaiterMatch // match on specific path
127 PathAnyWaiterMatch // match on any path
128 PathListWaiterMatch // match on list of paths
129 StatusWaiterMatch // match on status code
130 ErrorWaiterMatch // match on error
131 )
132
133 // String returns the string representation of the waiter match mode.
134 func (m WaiterMatchMode) String() string {
135 switch m {
136 case PathAllWaiterMatch:
137 return "pathAll"
138 case PathWaiterMatch:
139 return "path"
140 case PathAnyWaiterMatch:
141 return "pathAny"
142 case PathListWaiterMatch:
143 return "pathList"
144 case StatusWaiterMatch:
145 return "status"
146 case ErrorWaiterMatch:
147 return "error"
148 default:
149 return "unknown waiter match mode"
150 }
151 }
152
153 // WaitWithContext will make requests for the API operation using NewRequest to
154 // build API requests. The request's response will be compared against the
155 // Waiter's Acceptors to determine the successful state of the resource the
156 // waiter is inspecting.
157 //
158 // The passed in context must not be nil. If it is nil a panic will occur. The
159 // Context will be used to cancel the waiter's pending requests and retry delays.
160 // Use aws.BackgroundContext if no context is available.
161 //
162 // The waiter will continue until the target state defined by the Acceptors,
163 // or the max attempts expires.
164 //
165 // Will return the WaiterResourceNotReadyErrorCode error code if the waiter's
166 // retryer ShouldRetry returns false. This normally will happen when the max
167 // wait attempts expires.
168 func (w Waiter) WaitWithContext(ctx aws.Context) error {
169
170 for attempt := 1; ; attempt++ {
171 req, err := w.NewRequest(w.RequestOptions)
172 if err != nil {
173 waiterLogf(w.Logger, "unable to create request %v", err)
174 return err
175 }
176 req.Handlers.Build.PushBack(MakeAddToUserAgentFreeFormHandler("Waiter"))
177 err = req.Send()
178
179 // See if any of the acceptors match the request's response, or error
180 for _, a := range w.Acceptors {
181 if matched, matchErr := a.match(w.Name, w.Logger, req, err); matched {
182 return matchErr
183 }
184 }
185
186 // The Waiter should only check the resource state MaxAttempts times
187 // This is here instead of in the for loop above to prevent delaying
188 // unnecessary when the waiter will not retry.
189 if attempt == w.MaxAttempts {
190 break
191 }
192
193 // Delay to wait before inspecting the resource again
194 delay := w.Delay(attempt)
195 if sleepFn := req.Config.SleepDelay; sleepFn != nil {
196 // Support SleepDelay for backwards compatibility and testing
197 sleepFn(delay)
198 } else if err := aws.SleepWithContext(ctx, delay); err != nil {
199 return awserr.New(CanceledErrorCode, "waiter context canceled", err)
200 }
201 }
202
203 return awserr.New(WaiterResourceNotReadyErrorCode, "exceeded wait attempts", nil)
204 }
205
206 // A WaiterAcceptor provides the information needed to wait for an API operation
207 // to complete.
208 type WaiterAcceptor struct {
209 State WaiterState
210 Matcher WaiterMatchMode
211 Argument string
212 Expected interface{}
213 }
214
215 // match returns if the acceptor found a match with the passed in request
216 // or error. True is returned if the acceptor made a match, error is returned
217 // if there was an error attempting to perform the match.
218 func (a *WaiterAcceptor) match(name string, l aws.Logger, req *Request, err error) (bool, error) {
219 result := false
220 var vals []interface{}
221
222 switch a.Matcher {
223 case PathAllWaiterMatch, PathWaiterMatch:
224 // Require all matches to be equal for result to match
225 vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
226 if len(vals) == 0 {
227 break
228 }
229 result = true
230 for _, val := range vals {
231 if !awsutil.DeepEqual(val, a.Expected) {
232 result = false
233 break
234 }
235 }
236 case PathAnyWaiterMatch:
237 // Only a single match needs to equal for the result to match
238 vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
239 for _, val := range vals {
240 if awsutil.DeepEqual(val, a.Expected) {
241 result = true
242 break
243 }
244 }
245 case PathListWaiterMatch:
246 // ignored matcher
247 case StatusWaiterMatch:
248 s := a.Expected.(int)
249 result = s == req.HTTPResponse.StatusCode
250 case ErrorWaiterMatch:
251 if aerr, ok := err.(awserr.Error); ok {
252 result = aerr.Code() == a.Expected.(string)
253 }
254 default:
255 waiterLogf(l, "WARNING: Waiter %s encountered unexpected matcher: %s",
256 name, a.Matcher)
257 }
258
259 if !result {
260 // If there was no matching result found there is nothing more to do
261 // for this response, retry the request.
262 return false, nil
263 }
264
265 switch a.State {
266 case SuccessWaiterState:
267 // waiter completed
268 return true, nil
269 case FailureWaiterState:
270 // Waiter failure state triggered
271 return true, awserr.New(WaiterResourceNotReadyErrorCode,
272 "failed waiting for successful resource state", err)
273 case RetryWaiterState:
274 // clear the error and retry the operation
275 return false, nil
276 default:
277 waiterLogf(l, "WARNING: Waiter %s encountered unexpected state: %s",
278 name, a.State)
279 return false, nil
280 }
281 }
282
283 func waiterLogf(logger aws.Logger, msg string, args ...interface{}) {
284 if logger != nil {
285 logger.Log(fmt.Sprintf(msg, args...))
286 }
287 }