aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/aws/aws-sdk-go/private/waiter/waiter.go
blob: b51e9449c105c1230b906ca8e85c462bf095a5f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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 {
			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)
				if len(vals) == 0 {
					break
				}
				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...))
	}
}