UNPKG

run-on-ssh

Version:

Run a node.js script on a given ssh server

233 lines (206 loc) 7.93 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _promise = require('promise'); var _promise2 = _interopRequireDefault(_promise); var _thenRequest = require('then-request'); var _thenRequest2 = _interopRequireDefault(_thenRequest); var _ssh = require('./ssh'); var _ssh2 = _interopRequireDefault(_ssh); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var BASE_URL = 'https://api.digitalocean.com/v2'; var DigiatlOcean = function () { function DigiatlOcean(token) { (0, _classCallCheck3.default)(this, DigiatlOcean); this._token = token; this._runningPolls = 0; this._rateLimit = { limit: 5000, remaining: 5000, reset: Math.floor(Date.now() / 1000) }; } (0, _createClass3.default)(DigiatlOcean, [{ key: '_timeToRateLimitReset', value: function _timeToRateLimitReset() { return Math.max(1000, this._rateLimit.reset * 1000 - Date.now()); } }, { key: 'request', value: function request(method, path, options) { var _this = this; path = path.replace(BASE_URL, ''); return new _promise2.default(function (resolve, reject) { var attempt = function attempt() { (0, _thenRequest2.default)(method, BASE_URL + path, (0, _extends3.default)({}, options || {}, { headers: (0, _extends3.default)({}, options && options.headers ? options.headers : {}, { authorization: 'Bearer ' + _this._token }) })).done(function (res) { if (res.headers['ratelimit-limit']) { _this._rateLimit = { limit: res.headers['ratelimit-limit'], remaining: res.headers['ratelimit-remaining'], reset: res.headers['ratelimit-reset'] }; if (res.statusCode === 429) { console.log('Rate limit exceeded, backing off for ' + _this._timeToRateLimitReset() / 1000 + ' seconds'); return setTimeout(attempt, _this._timeToRateLimitReset()); } } resolve(res); }, reject); }; attempt(); }); } }, { key: 'requestJSON', value: function requestJSON(method, path, options) { return this.request(method, path, options).then(function (res) { return JSON.parse(res.getBody('utf8')); }); } }, { key: 'getPaged', value: function getPaged(path, key, filter) { var _this2 = this; return new _promise2.default(function (resolve, reject) { var result = []; var nextPage = function nextPage(url) { _this2.requestJSON('get', url).done(function (response) { result.push.apply(result, (0, _toConsumableArray3.default)(filter ? response[key].filter(filter) : response[key])); if (response.links && response.links.pages && response.links.pages.next) { nextPage(response.links.pages.next); } else { resolve(result); } }, reject); }; nextPage(path); }); } }, { key: 'poll', value: function poll(path, isReady, timeout) { var _this3 = this; this._runningPolls++; timeout = timeout || 60 * 1000; var timeoutEnd = Date.now() + timeout; return new _promise2.default(function (resolve, reject) { var poll = function poll(finalAttempt) { _this3.requestJSON('get', path).then(function (result) { if (isReady(result)) { resolve(result); } else if (finalAttempt) { reject(new Error('Operation timed out after ' + timeout / 1000 + ' seconds')); } else if (Date.now() > timeoutEnd) { poll(true); } else { // poll less frequently for operations that are typically slower // poll less frequently if we are running out of rate limit var timeToRateLimitReset = _this3._timeToRateLimitReset(); // console.log( // this._rateLimit.remaining + ' remaining of ' + this._rateLimit.limit + ' with reset in ' + // (timeToRateLimitReset / 1000) + ' seconds' // ); var remaining = (_this3._rateLimit.remaining || 1) / _this3._runningPolls; // console.log('backoff: ' + (remaining / 1000)); setTimeout(function () { return poll(); }, timeout / 60 + timeToRateLimitReset / remaining); } }).done(null, reject); }; poll(); }).finally(function () { return _this3._runningPolls--; }); } }, { key: 'connect', value: function connect(droplet, options) { var addresses = droplet.networks.v4.filter(function (n) { return n.type === 'public'; }); if (addresses.length === 0) { throw new Error('Could not find the IP address of droplet'); } var connection = new _ssh2.default({ host: addresses[0].ip_address, port: 22, username: 'root', privateKey: this._privateKey }, options); return connection.ready.then(function () { return connection; }); } }, { key: 'runAction', value: function runAction(path, action, options) { var _this4 = this; return this.requestJSON('post', path + '/actions', { json: action }).then(function (result) { var actions = result.actions ? result.actions : [result.action]; return _promise2.default.all(actions.map(function (_ref) { var id = _ref.id; return _this4.poll('/actions/' + id, function (result) { return result.action.status !== 'in-progress'; }, options && options.timeout).then(function (result) { if (result.action.status !== 'completed') { throw new Error(action.type + ' did not complete successfully'); } }); })); }).then(function () {}); } }, { key: 'renameDroplet', value: function renameDroplet(droplet, name) { return this._runAction('/droplets/' + droplet.id, { type: 'rename', name: name }).then(function () { return droplet; }); } }, { key: 'rebuildDroplet', value: function rebuildDroplet(droplet, image) { console.log('rebuild droplet'); return this._runAction('/droplets/' + droplet.id, { type: 'rebuild', image: image }).then(function () { return droplet; }); } }, { key: 'shutdownDroplet', value: function shutdownDroplet(droplet) { var _this5 = this; console.log('shutdown droplet'); return new _promise2.default(function (resolve, reject) { var attempt = function attempt(attemptNumber) { _this5.runAction('/droplets/' + droplet.id, { type: 'shutdown' }).then(function () { return _this5.poll('/droplets/' + droplet.id, function (result) { return result.droplet.status === 'off'; }); }).done(resolve, function (err) { if (attemptNumber < 3) { attempt(attemptNumber + 1); } else { reject(err); } }); }; attempt(0); }); } }]); return DigiatlOcean; }(); exports.default = DigiatlOcean;