UNPKG

berlioz

Version:

Berlioz - cloud deployment and migration services

340 lines (300 loc) 10.4 kB
const request = require('request-promise'); const _ = require('the-lodash'); const Errors = require('./errors'); const fs = require('fs'); const yaml = require('js-yaml'); const DateDiff = require('date-diff'); const SECTION_AAA = 'aaa'; const SECTION_API = 'v1'; class BerliozClient { constructor(logger, screen, configRegistry, environment) { this._screen = screen; this._environment = environment; this._endpoints = { global: 'https://api-global.berlioz.cloud/global' } var endpointOverride = this._environment.getValue('BERLIOZ_ENDPOINT_OVERRIDE'); if (endpointOverride) { var fileContentsStr = fs.readFileSync(endpointOverride); var fileContents = yaml.safeLoad(fileContentsStr); if (!fileContents.endpoints) { fileContents.endpoints = {}; } this._endpoints = _.defaults(fileContents.endpoints, this._endpoints); screen.header('BERLIOZ ENDPOINTS OVERRIDDEN'); for(var x of _.keys(this._endpoints)) { screen.info('EP: %s => %s', x, this._endpoints[x]); } screen.line(); } this._logger = logger; if (!logger) { this._logger = { info: function() {}, warning: function() {}, error: function() {}, exception: function() {}, } } this._configRegistry = configRegistry; this._shouldSkipSsl = false; } setSkipSSL(value) { this._shouldSkipSsl = value; } _queryRegions() { return this._rawRequest('GET', this._endpoints.global, '/regions') .then(data => { this._configRegistry.set('global', 'rawRegions', data); this._configRegistry.set('global', 'regionMapping', data.mapping); this._configRegistry.set('global', 'providerMapping', data.providers); this._configRegistry.set('global', 'masterRegion', data.masterRegion); this._configRegistry.set('global', 'lastCheck', new Date().toISOString()); this._configRegistry.set('global', 'sourceEndpoints', this._endpoints); }); } _fetchRegions() { if (!this._shouldRefreshRegions()) { return; } return this._queryRegions(); } _shouldRefreshRegions() { var lastCheckStr = this._configRegistry.get('global', 'lastCheck'); if (!lastCheckStr) { return true; } var sourceEndpoints = this._configRegistry.get('global', 'sourceEndpoints'); if (!sourceEndpoints) { return true; } if (!_.fastDeepEqual(sourceEndpoints, this._endpoints)) { return true; } var lastCheck = new Date(lastCheckStr); var diff = new DateDiff(new Date(), lastCheck); if (diff.seconds() < 5 * 60) { return false; } return true; } _performInMaster(action) { return Promise.resolve(this._getFromGlobal('masterRegion')) .then(region => { if (!region) { throw new Error('Master region not present. Please try again later.'); } return action(region); }); } login(username, password) { return Promise.resolve() .then(() => this.logout()) .then(() => this.requestToAAA('POST', '/login', { user: username, pass: password })) .then(result => this._handlePostLogin(result)); } _handlePostLogin(result) { if (result) { if (result.success) { this._configRegistry.set('auth', ['token', this._endpoints.global], result); return; } } throw new Error('Failed to log in'); } _refreshToken(refreshToken) { return Promise.resolve() .then(() => this.requestToAAA('POST', '/refresh', { refresh_token: refreshToken })) .then(result => this._handlePostLogin(result)); } logout() { return Promise.resolve() .then(() => { return this._configRegistry.clear('auth', ['token', this._endpoints.global]); }) } requestToAAA(method, path, data) { return this._performInMaster(region => { return this._requestToRegion(region, method, SECTION_AAA, path, data, true) }) } getRegions() { return Promise.resolve(this._getFromGlobal('providerMapping')) .then(result => { return _.keys(result).map(x => ({ name: x, provider: result[x] })) }); } ////////////////////////////////////// post(region, path, data) { return this._requestToRegion(region, 'POST', SECTION_API, path, data); } postMaster(path, data) { return this._performInMaster(region => { return this.post(region, path, data); }) } get(region, path) { return this._requestToRegion(region, 'GET', SECTION_API, path); } getMaster(path) { return this._performInMaster(region => { return this.get(region, path); }) } delete(region, path, data) { return this._requestToRegion(region, 'DELETE', SECTION_API, path, data); } deleteMaster(path, data) { return this._performInMaster(region => { return this.delete(region, path, data); }) } _requestToRegion(region, method, baseSection, path, data, skipAuth) { return Promise.resolve(this._resolveRegionBaseUrl(region)) .then(baseUrl => { var targetUrl = baseUrl + '/' + baseSection; if (skipAuth) { return this._rawRequest(method, targetUrl, path, data, null); } else { return this._requestToRegionX(region, method, baseUrl, targetUrl, path, data, true); } }); } _requestToRegionX(region, method, baseUrl, targetUrl, path, data, tryRefreshToken) { var tokenObj = this._configRegistry.get('auth', ['token', this._endpoints.global]); if (!tokenObj || !tokenObj.access_token) { throw new Errors.Auth("Not logged in."); } return this._rawRequest(method, targetUrl, path, data, tokenObj.access_token) .catch(reason => { if (tryRefreshToken) { if (reason instanceof Errors.Auth) { return this._refreshToken(tokenObj.refresh_token) .then(() => this._requestToRegionX(region, method, baseUrl, targetUrl, path, data, false)) } } throw reason; }); } _resolveRegionBaseUrl(region) { if (!region) { throw new Error("Region not set!") } return Promise.resolve(this._getFromGlobal('regionMapping')) .then(regionMapping => { var baseUrl = regionMapping[region]; if (!baseUrl) { throw new Error(`Region \"${region}\" not supported.`); } return baseUrl; }); } _getFromGlobal(name) { return Promise.resolve(this._fetchRegions()) .then(() => this._configRegistry.get('global', name)); } _rawRequest(method, baseUrl, path, data, token) { var options = { method: method, uri: baseUrl + path, body: data, json: true }; if (this._shouldSkipSsl) { options.strictSSL = false; } if (token) { options.headers = { 'Authorization': token } } this._logger.info('RAW REQUEST:', options); return request(options) .then(result => { this._logger.info('RESPONSE:', result); return result; }) .catch(err => { // this._logger.error('FAILED REQUEST:', options); this._logger.info('[_rawRequest] ERROR:', err); this._massageErrorResponse(err); this._logger.error('[_rawRequest] RESPONSE ERROR: %s', err.message); this._logger.error('[_rawRequest] URL: %s', options.uri); this._logger.error('[_rawRequest] ALL OPTIONS: ', options); this._logger.exception(err); throw err; }); } _massageErrorResponse(response) { // this._logger.error('[_massageErrorResponse]', response); // this._logger.error('[_massageErrorResponse] statusCode: %s', response.statusCode); if (response) { if ((response.statusCode == 401) || (response.statusCode == 403)) { var msg = this._getErrorFromResponse(response); throw new Errors.Auth(msg); } if (response.statusCode == 400) { var msg = this._getErrorFromResponse(response); throw new Errors.Input(msg); } this._logger.error('RESPONSE ERROR MSG: ', response.error); this._logger.error('RESPONSE STATUS CODE: %s', response.statusCode); this._logger.error('RESPONSE CODE: %s', response.code); this._logger.error('Please contact support@berlioz.cloud for help.'); } } _getErrorFromResponse(response) { if (response) { if (response.error) { if (response.error.message) { return response.error.message; } if (response.error.error) { return response.error.error; } } } return 'Unknown error. Please refer to logs or contact support@berlioz.cloud.'; } } module.exports = BerliozClient;