1// +build codegen 2 3package api 4 5import ( 6 "bytes" 7 "encoding/json" 8 "fmt" 9 "os" 10 "sort" 11 "strings" 12 "text/template" 13) 14 15// WaiterAcceptor is the acceptors defined in the model the SDK will use 16// to wait on resource states with. 17type WaiterAcceptor struct { 18 State string 19 Matcher string 20 Argument string 21 Expected interface{} 22} 23 24// ExpectedString returns the string that was expected by the WaiterAcceptor 25func (a *WaiterAcceptor) ExpectedString() string { 26 switch a.Expected.(type) { 27 case string: 28 return fmt.Sprintf("%q", a.Expected) 29 default: 30 return fmt.Sprintf("%v", a.Expected) 31 } 32} 33 34// A Waiter is an individual waiter definition. 35type Waiter struct { 36 Name string 37 Delay int 38 MaxAttempts int 39 OperationName string `json:"operation"` 40 Operation *Operation 41 Acceptors []WaiterAcceptor 42} 43 44// WaitersGoCode generates and returns Go code for each of the waiters of 45// this API. 46func (a *API) WaitersGoCode() string { 47 var buf bytes.Buffer 48 fmt.Fprintf(&buf, "import (\n%q\n\n%q\n%q\n)", 49 "time", 50 SDKImportRoot+"/aws", 51 SDKImportRoot+"/aws/request", 52 ) 53 54 for _, w := range a.Waiters { 55 buf.WriteString(w.GoCode()) 56 } 57 return buf.String() 58} 59 60// used for unmarshaling from the waiter JSON file 61type waiterDefinitions struct { 62 *API 63 Waiters map[string]Waiter 64} 65 66// AttachWaiters reads a file of waiter definitions, and adds those to the API. 67// Will panic if an error occurs. 68func (a *API) AttachWaiters(filename string) error { 69 p := waiterDefinitions{API: a} 70 71 f, err := os.Open(filename) 72 defer f.Close() 73 if err != nil { 74 return err 75 } 76 err = json.NewDecoder(f).Decode(&p) 77 if err != nil { 78 return err 79 } 80 81 return p.setup() 82} 83 84func (p *waiterDefinitions) setup() error { 85 p.API.Waiters = []Waiter{} 86 i, keys := 0, make([]string, len(p.Waiters)) 87 for k := range p.Waiters { 88 keys[i] = k 89 i++ 90 } 91 sort.Strings(keys) 92 93 for _, n := range keys { 94 e := p.Waiters[n] 95 n = p.ExportableName(n) 96 e.Name = n 97 e.OperationName = p.ExportableName(e.OperationName) 98 e.Operation = p.API.Operations[e.OperationName] 99 if e.Operation == nil { 100 return fmt.Errorf("unknown operation %s for waiter %s", 101 e.OperationName, n) 102 } 103 p.API.Waiters = append(p.API.Waiters, e) 104 } 105 106 return nil 107} 108 109var waiterTmpls = template.Must(template.New("waiterTmpls").Funcs( 110 template.FuncMap{ 111 "titleCase": func(v string) string { 112 return strings.Title(v) 113 }, 114 }, 115).Parse(` 116{{ define "waiter"}} 117// WaitUntil{{ .Name }} uses the {{ .Operation.API.NiceName }} API operation 118// {{ .OperationName }} to wait for a condition to be met before returning. 119// If the condition is not met within the max attempt window, an error will 120// be returned. 121func (c *{{ .Operation.API.StructName }}) WaitUntil{{ .Name }}(input {{ .Operation.InputRef.GoType }}) error { 122 return c.WaitUntil{{ .Name }}WithContext(aws.BackgroundContext(), input) 123} 124 125// WaitUntil{{ .Name }}WithContext is an extended version of WaitUntil{{ .Name }}. 126// With the support for passing in a context and options to configure the 127// Waiter and the underlying request options. 128// 129// The context must be non-nil and will be used for request cancellation. If 130// the context is nil a panic will occur. In the future the SDK may create 131// sub-contexts for http.Requests. See https://golang.org/pkg/context/ 132// for more information on using Contexts. 133func (c *{{ .Operation.API.StructName }}) WaitUntil{{ .Name }}WithContext(` + 134 `ctx aws.Context, input {{ .Operation.InputRef.GoType }}, opts ...request.WaiterOption) error { 135 w := request.Waiter{ 136 Name: "WaitUntil{{ .Name }}", 137 MaxAttempts: {{ .MaxAttempts }}, 138 Delay: request.ConstantWaiterDelay({{ .Delay }} * time.Second), 139 Acceptors: []request.WaiterAcceptor{ 140 {{ range $_, $a := .Acceptors }}{ 141 State: request.{{ titleCase .State }}WaiterState, 142 Matcher: request.{{ titleCase .Matcher }}WaiterMatch, 143 {{- if .Argument }}Argument: "{{ .Argument }}",{{ end }} 144 Expected: {{ .ExpectedString }}, 145 }, 146 {{ end }} 147 }, 148 Logger: c.Config.Logger, 149 NewRequest: func(opts []request.Option) (*request.Request, error) { 150 var inCpy {{ .Operation.InputRef.GoType }} 151 if input != nil { 152 tmp := *input 153 inCpy = &tmp 154 } 155 req, _ := c.{{ .OperationName }}Request(inCpy) 156 req.SetContext(ctx) 157 req.ApplyOptions(opts...) 158 return req, nil 159 }, 160 } 161 w.ApplyOptions(opts...) 162 163 return w.WaitWithContext(ctx) 164} 165{{- end }} 166 167{{ define "waiter interface" }} 168WaitUntil{{ .Name }}({{ .Operation.InputRef.GoTypeWithPkgName }}) error 169WaitUntil{{ .Name }}WithContext(aws.Context, {{ .Operation.InputRef.GoTypeWithPkgName }}, ...request.WaiterOption) error 170{{- end }} 171`)) 172 173// InterfaceSignature returns a string representing the Waiter's interface 174// function signature. 175func (w *Waiter) InterfaceSignature() string { 176 var buf bytes.Buffer 177 if err := waiterTmpls.ExecuteTemplate(&buf, "waiter interface", w); err != nil { 178 panic(err) 179 } 180 181 return strings.TrimSpace(buf.String()) 182} 183 184// GoCode returns the generated Go code for an individual waiter. 185func (w *Waiter) GoCode() string { 186 var buf bytes.Buffer 187 if err := waiterTmpls.ExecuteTemplate(&buf, "waiter", w); err != nil { 188 panic(err) 189 } 190 191 return buf.String() 192} 193