resource_waiter.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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. var AWS = require('./core');
  16. var inherit = AWS.util.inherit;
  17. var jmespath = require('jmespath');
  18. /**
  19. * @api private
  20. */
  21. function CHECK_ACCEPTORS(resp) {
  22. var waiter = resp.request._waiter;
  23. var acceptors = waiter.config.acceptors;
  24. var acceptorMatched = false;
  25. var state = 'retry';
  26. acceptors.forEach(function(acceptor) {
  27. if (!acceptorMatched) {
  28. var matcher = waiter.matchers[acceptor.matcher];
  29. if (matcher && matcher(resp, acceptor.expected, acceptor.argument)) {
  30. acceptorMatched = true;
  31. state = acceptor.state;
  32. }
  33. }
  34. });
  35. if (!acceptorMatched && resp.error) state = 'failure';
  36. if (state === 'success') {
  37. waiter.setSuccess(resp);
  38. } else {
  39. waiter.setError(resp, state === 'retry');
  40. }
  41. }
  42. /**
  43. * @api private
  44. */
  45. AWS.ResourceWaiter = inherit({
  46. /**
  47. * Waits for a given state on a service object
  48. * @param service [Service] the service object to wait on
  49. * @param state [String] the state (defined in waiter configuration) to wait
  50. * for.
  51. * @example Create a waiter for running EC2 instances
  52. * var ec2 = new AWS.EC2;
  53. * var waiter = new AWS.ResourceWaiter(ec2, 'instanceRunning');
  54. */
  55. constructor: function constructor(service, state) {
  56. this.service = service;
  57. this.state = state;
  58. this.loadWaiterConfig(this.state);
  59. },
  60. service: null,
  61. state: null,
  62. config: null,
  63. matchers: {
  64. path: function(resp, expected, argument) {
  65. try {
  66. var result = jmespath.search(resp.data, argument);
  67. } catch (err) {
  68. return false;
  69. }
  70. return jmespath.strictDeepEqual(result,expected);
  71. },
  72. pathAll: function(resp, expected, argument) {
  73. try {
  74. var results = jmespath.search(resp.data, argument);
  75. } catch (err) {
  76. return false;
  77. }
  78. if (!Array.isArray(results)) results = [results];
  79. var numResults = results.length;
  80. if (!numResults) return false;
  81. for (var ind = 0 ; ind < numResults; ind++) {
  82. if (!jmespath.strictDeepEqual(results[ind], expected)) {
  83. return false;
  84. }
  85. }
  86. return true;
  87. },
  88. pathAny: function(resp, expected, argument) {
  89. try {
  90. var results = jmespath.search(resp.data, argument);
  91. } catch (err) {
  92. return false;
  93. }
  94. if (!Array.isArray(results)) results = [results];
  95. var numResults = results.length;
  96. for (var ind = 0 ; ind < numResults; ind++) {
  97. if (jmespath.strictDeepEqual(results[ind], expected)) {
  98. return true;
  99. }
  100. }
  101. return false;
  102. },
  103. status: function(resp, expected) {
  104. var statusCode = resp.httpResponse.statusCode;
  105. return (typeof statusCode === 'number') && (statusCode === expected);
  106. },
  107. error: function(resp, expected) {
  108. if (typeof expected === 'string' && resp.error) {
  109. return expected === resp.error.code;
  110. }
  111. // if expected is not string, can be boolean indicating presence of error
  112. return expected === !!resp.error;
  113. }
  114. },
  115. listeners: new AWS.SequentialExecutor().addNamedListeners(function(add) {
  116. add('RETRY_CHECK', 'retry', function(resp) {
  117. var waiter = resp.request._waiter;
  118. if (resp.error && resp.error.code === 'ResourceNotReady') {
  119. resp.error.retryDelay = (waiter.config.delay || 0) * 1000;
  120. }
  121. });
  122. add('CHECK_OUTPUT', 'extractData', CHECK_ACCEPTORS);
  123. add('CHECK_ERROR', 'extractError', CHECK_ACCEPTORS);
  124. }),
  125. /**
  126. * @return [AWS.Request]
  127. */
  128. wait: function wait(params, callback) {
  129. if (typeof params === 'function') {
  130. callback = params; params = undefined;
  131. }
  132. if (params && params.$waiter) {
  133. params = AWS.util.copy(params);
  134. if (typeof params.$waiter.delay === 'number') {
  135. this.config.delay = params.$waiter.delay;
  136. }
  137. if (typeof params.$waiter.maxAttempts === 'number') {
  138. this.config.maxAttempts = params.$waiter.maxAttempts;
  139. }
  140. delete params.$waiter;
  141. }
  142. var request = this.service.makeRequest(this.config.operation, params);
  143. request._waiter = this;
  144. request.response.maxRetries = this.config.maxAttempts;
  145. request.addListeners(this.listeners);
  146. if (callback) request.send(callback);
  147. return request;
  148. },
  149. setSuccess: function setSuccess(resp) {
  150. resp.error = null;
  151. resp.data = resp.data || {};
  152. resp.request.removeAllListeners('extractData');
  153. },
  154. setError: function setError(resp, retryable) {
  155. resp.data = null;
  156. resp.error = AWS.util.error(resp.error || new Error(), {
  157. code: 'ResourceNotReady',
  158. message: 'Resource is not in the state ' + this.state,
  159. retryable: retryable
  160. });
  161. },
  162. /**
  163. * Loads waiter configuration from API configuration
  164. *
  165. * @api private
  166. */
  167. loadWaiterConfig: function loadWaiterConfig(state) {
  168. if (!this.service.api.waiters[state]) {
  169. throw new AWS.util.error(new Error(), {
  170. code: 'StateNotFoundError',
  171. message: 'State ' + state + ' not found.'
  172. });
  173. }
  174. this.config = AWS.util.copy(this.service.api.waiters[state]);
  175. }
  176. });