UNPKG

5.69 kBJavaScriptView Raw
1/**
2 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"). You
5 * may not use this file except in compliance with the License. A copy of
6 * the License is located at
7 *
8 * http://aws.amazon.com/apache2.0/
9 *
10 * or in the "license" file accompanying this file. This file is
11 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 * ANY KIND, either express or implied. See the License for the specific
13 * language governing permissions and limitations under the License.
14 */
15
16var AWS = require('./core');
17var inherit = AWS.util.inherit;
18var jmespath = require('jmespath');
19
20/**
21 * @api private
22 */
23function CHECK_ACCEPTORS(resp) {
24 var waiter = resp.request._waiter;
25 var acceptors = waiter.config.acceptors;
26 var acceptorMatched = false;
27 var state = 'retry';
28
29 acceptors.forEach(function(acceptor) {
30 if (!acceptorMatched) {
31 var matcher = waiter.matchers[acceptor.matcher];
32 if (matcher && matcher(resp, acceptor.expected, acceptor.argument)) {
33 acceptorMatched = true;
34 state = acceptor.state;
35 }
36 }
37 });
38
39 if (!acceptorMatched && resp.error) state = 'failure';
40
41 if (state === 'success') {
42 waiter.setSuccess(resp);
43 } else {
44 waiter.setError(resp, state === 'retry');
45 }
46}
47
48/**
49 * @api private
50 */
51AWS.ResourceWaiter = inherit({
52 /**
53 * Waits for a given state on a service object
54 * @param service [Service] the service object to wait on
55 * @param state [String] the state (defined in waiter configuration) to wait
56 * for.
57 * @example Create a waiter for running EC2 instances
58 * var ec2 = new AWS.EC2;
59 * var waiter = new AWS.ResourceWaiter(ec2, 'instanceRunning');
60 */
61 constructor: function constructor(service, state) {
62 this.service = service;
63 this.state = state;
64 this.loadWaiterConfig(this.state);
65 },
66
67 service: null,
68
69 state: null,
70
71 config: null,
72
73 matchers: {
74 path: function(resp, expected, argument) {
75 try {
76 var result = jmespath.search(resp.data, argument);
77 } catch (err) {
78 return false;
79 }
80
81 return jmespath.strictDeepEqual(result,expected);
82 },
83
84 pathAll: function(resp, expected, argument) {
85 try {
86 var results = jmespath.search(resp.data, argument);
87 } catch (err) {
88 return false;
89 }
90
91 if (!Array.isArray(results)) results = [results];
92 var numResults = results.length;
93 if (!numResults) return false;
94 for (var ind = 0 ; ind < numResults; ind++) {
95 if (!jmespath.strictDeepEqual(results[ind], expected)) {
96 return false;
97 }
98 }
99 return true;
100 },
101
102 pathAny: function(resp, expected, argument) {
103 try {
104 var results = jmespath.search(resp.data, argument);
105 } catch (err) {
106 return false;
107 }
108
109 if (!Array.isArray(results)) results = [results];
110 var numResults = results.length;
111 for (var ind = 0 ; ind < numResults; ind++) {
112 if (jmespath.strictDeepEqual(results[ind], expected)) {
113 return true;
114 }
115 }
116 return false;
117 },
118
119 status: function(resp, expected) {
120 var statusCode = resp.httpResponse.statusCode;
121 return (typeof statusCode === 'number') && (statusCode === expected);
122 },
123
124 error: function(resp, expected) {
125 if (typeof expected === 'string' && resp.error) {
126 return expected === resp.error.code;
127 }
128 // if expected is not string, can be boolean indicating presence of error
129 return expected === !!resp.error;
130 }
131 },
132
133 listeners: new AWS.SequentialExecutor().addNamedListeners(function(add) {
134 add('RETRY_CHECK', 'retry', function(resp) {
135 var waiter = resp.request._waiter;
136 if (resp.error && resp.error.code === 'ResourceNotReady') {
137 resp.error.retryDelay = (waiter.config.delay || 0) * 1000;
138 }
139 });
140
141 add('CHECK_OUTPUT', 'extractData', CHECK_ACCEPTORS);
142
143 add('CHECK_ERROR', 'extractError', CHECK_ACCEPTORS);
144 }),
145
146 /**
147 * @return [AWS.Request]
148 */
149 wait: function wait(params, callback) {
150 if (typeof params === 'function') {
151 callback = params; params = undefined;
152 }
153
154 if (params && params.$waiter) {
155 params = AWS.util.copy(params);
156 if (typeof params.$waiter.delay === 'number') {
157 this.config.delay = params.$waiter.delay;
158 }
159 if (typeof params.$waiter.maxAttempts === 'number') {
160 this.config.maxAttempts = params.$waiter.maxAttempts;
161 }
162 delete params.$waiter;
163 }
164
165 var request = this.service.makeRequest(this.config.operation, params);
166 request._waiter = this;
167 request.response.maxRetries = this.config.maxAttempts;
168 request.addListeners(this.listeners);
169
170 if (callback) request.send(callback);
171 return request;
172 },
173
174 setSuccess: function setSuccess(resp) {
175 resp.error = null;
176 resp.data = resp.data || {};
177 resp.request.removeAllListeners('extractData');
178 },
179
180 setError: function setError(resp, retryable) {
181 resp.data = null;
182 resp.error = AWS.util.error(resp.error || new Error(), {
183 code: 'ResourceNotReady',
184 message: 'Resource is not in the state ' + this.state,
185 retryable: retryable
186 });
187 },
188
189 /**
190 * Loads waiter configuration from API configuration
191 *
192 * @api private
193 */
194 loadWaiterConfig: function loadWaiterConfig(state) {
195 if (!this.service.api.waiters[state]) {
196 throw new AWS.util.error(new Error(), {
197 code: 'StateNotFoundError',
198 message: 'State ' + state + ' not found.'
199 });
200 }
201
202 this.config = AWS.util.copy(this.service.api.waiters[state]);
203 }
204});