137 lines
3.4 KiB
Go
137 lines
3.4 KiB
Go
package waiter
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
|
"github.com/aws/aws-sdk-go/aws/request"
|
|
)
|
|
|
|
// A Config provides a collection of configuration values to setup a generated
|
|
// waiter code with.
|
|
type Config struct {
|
|
Name string
|
|
Delay int
|
|
MaxAttempts int
|
|
Operation string
|
|
Acceptors []WaitAcceptor
|
|
}
|
|
|
|
// A WaitAcceptor provides the information needed to wait for an API operation
|
|
// to complete.
|
|
type WaitAcceptor struct {
|
|
Expected interface{}
|
|
Matcher string
|
|
State string
|
|
Argument string
|
|
}
|
|
|
|
// A Waiter provides waiting for an operation to complete.
|
|
type Waiter struct {
|
|
Config
|
|
Client interface{}
|
|
Input interface{}
|
|
}
|
|
|
|
// Wait waits for an operation to complete, expire max attempts, or fail. Error
|
|
// is returned if the operation fails.
|
|
func (w *Waiter) Wait() error {
|
|
client := reflect.ValueOf(w.Client)
|
|
in := reflect.ValueOf(w.Input)
|
|
method := client.MethodByName(w.Config.Operation + "Request")
|
|
|
|
for i := 0; i < w.MaxAttempts; i++ {
|
|
res := method.Call([]reflect.Value{in})
|
|
req := res[0].Interface().(*request.Request)
|
|
req.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Waiter"))
|
|
|
|
err := req.Send()
|
|
for _, a := range w.Acceptors {
|
|
if err != nil && a.Matcher != "error" {
|
|
// Only matcher error is valid if there is a request error
|
|
continue
|
|
}
|
|
|
|
result := false
|
|
var vals []interface{}
|
|
switch a.Matcher {
|
|
case "pathAll", "path":
|
|
// Require all matches to be equal for result to match
|
|
vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
|
|
result = true
|
|
for _, val := range vals {
|
|
if !awsutil.DeepEqual(val, a.Expected) {
|
|
result = false
|
|
break
|
|
}
|
|
}
|
|
case "pathAny":
|
|
// Only a single match needs to equal for the result to match
|
|
vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
|
|
for _, val := range vals {
|
|
if awsutil.DeepEqual(val, a.Expected) {
|
|
result = true
|
|
break
|
|
}
|
|
}
|
|
case "status":
|
|
s := a.Expected.(int)
|
|
result = s == req.HTTPResponse.StatusCode
|
|
case "error":
|
|
if aerr, ok := err.(awserr.Error); ok {
|
|
result = aerr.Code() == a.Expected.(string)
|
|
}
|
|
case "pathList":
|
|
// ignored matcher
|
|
default:
|
|
logf(client, "WARNING: Waiter for %s encountered unexpected matcher: %s",
|
|
w.Config.Operation, a.Matcher)
|
|
}
|
|
|
|
if !result {
|
|
// If there was no matching result found there is nothing more to do
|
|
// for this response, retry the request.
|
|
continue
|
|
}
|
|
|
|
switch a.State {
|
|
case "success":
|
|
// waiter completed
|
|
return nil
|
|
case "failure":
|
|
// Waiter failure state triggered
|
|
return awserr.New("ResourceNotReady",
|
|
fmt.Sprintf("failed waiting for successful resource state"), err)
|
|
case "retry":
|
|
// clear the error and retry the operation
|
|
err = nil
|
|
default:
|
|
logf(client, "WARNING: Waiter for %s encountered unexpected state: %s",
|
|
w.Config.Operation, a.State)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
time.Sleep(time.Second * time.Duration(w.Delay))
|
|
}
|
|
|
|
return awserr.New("ResourceNotReady",
|
|
fmt.Sprintf("exceeded %d wait attempts", w.MaxAttempts), nil)
|
|
}
|
|
|
|
func logf(client reflect.Value, msg string, args ...interface{}) {
|
|
cfgVal := client.FieldByName("Config")
|
|
if !cfgVal.IsValid() {
|
|
return
|
|
}
|
|
if cfg, ok := cfgVal.Interface().(*aws.Config); ok && cfg.Logger != nil {
|
|
cfg.Logger.Log(fmt.Sprintf(msg, args...))
|
|
}
|
|
}
|