UNPKG

bit-bin

Version:

<a href="https://opensource.org/licenses/Apache-2.0"><img alt="apache" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a> <a href="https://github.com/teambit/bit/blob/master/CONTRIBUTING.md"><img alt="prs" src="https://img.shields.io/b

875 lines (669 loc) 23.6 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.DEFAULT_READ_STRATEGIES = exports.DEFAULT_STRATEGIES = void 0; function _bluebird() { const data = require("bluebird"); _bluebird = function () { return data; }; return data; } function _defineProperty2() { const data = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); _defineProperty2 = function () { return data; }; return data; } function _ssh() { const data = _interopRequireDefault(require("ssh2")); _ssh = function () { return data; }; return data; } function _ramda() { const data = _interopRequireDefault(require("ramda")); _ramda = function () { return data; }; return data; } function os() { const data = _interopRequireWildcard(require("os")); os = function () { return data; }; return data; } function _lodash() { const data = _interopRequireDefault(require("lodash.merge")); _lodash = function () { return data; }; return data; } function _prompts() { const data = require("../../../prompts"); _prompts = function () { return data; }; return data; } function _keyGetter() { const data = _interopRequireDefault(require("./key-getter")); _keyGetter = function () { return data; }; return data; } function _componentObjects() { const data = _interopRequireDefault(require("../../component-objects")); _componentObjects = function () { return data; }; return data; } function _exceptions() { const data = require("../exceptions"); _exceptions = function () { return data; }; return data; } function _bitId() { const data = require("../../../bit-id"); _bitId = function () { return data; }; return data; } function _utils() { const data = require("../../../utils"); _utils = function () { return data; }; return data; } function _componentNotFound() { const data = _interopRequireDefault(require("../../../scope/exceptions/component-not-found")); _componentNotFound = function () { return data; }; return data; } function _component() { const data = _interopRequireDefault(require("../../../consumer/component")); _component = function () { return data; }; return data; } function _checkVersionCompatibility() { const data = _interopRequireDefault(require("../check-version-compatibility")); _checkVersionCompatibility = function () { return data; }; return data; } function _logger() { const data = _interopRequireDefault(require("../../../logger/logger")); _logger = function () { return data; }; return data; } function _constants() { const data = require("../../../constants"); _constants = function () { return data; }; return data; } function _removedComponents() { const data = _interopRequireDefault(require("../../removed-components")); _removedComponents = function () { return data; }; return data; } function _mergeConflictOnRemote() { const data = _interopRequireDefault(require("../../exceptions/merge-conflict-on-remote")); _mergeConflictOnRemote = function () { return data; }; return data; } function _analytics() { const data = require("../../../analytics/analytics"); _analytics = function () { return data; }; return data; } function globalConfig() { const data = _interopRequireWildcard(require("../../../api/consumer/lib/global-config")); globalConfig = function () { return data; }; return data; } function _generalError() { const data = _interopRequireDefault(require("../../../error/general-error")); _generalError = function () { return data; }; return data; } function _customError() { const data = _interopRequireDefault(require("../../../error/custom-error")); _customError = function () { return data; }; return data; } function _exportAnotherOwnerPrivate() { const data = _interopRequireDefault(require("../exceptions/export-another-owner-private")); _exportAnotherOwnerPrivate = function () { return data; }; return data; } function _scopeGraph() { const data = _interopRequireDefault(require("../../graph/scope-graph")); _scopeGraph = function () { return data; }; return data; } function _globalFlags() { const data = _interopRequireDefault(require("../../../cli/global-flags")); _globalFlags = function () { return data; }; return data; } /* eslint max-classes-per-file: 0 */ const checkVersionCompatibility = _ramda().default.once(_checkVersionCompatibility().default); const AUTH_FAILED_MESSAGE = 'All configured authentication methods failed'; const PASSPHRASE_POSSIBLY_MISSING_MESSAGE = 'Cannot parse privateKey: Unsupported key format'; function absolutePath(path) { if (!path.startsWith('/')) return `~/${path}`; return path; } function clean(str) { return str.replace('\n', ''); } class AuthenticationStrategyFailed extends Error {} const DEFAULT_STRATEGIES = ['token', 'ssh-agent', 'ssh-key', 'user-password']; exports.DEFAULT_STRATEGIES = DEFAULT_STRATEGIES; const DEFAULT_READ_STRATEGIES = ['token', 'ssh-agent', 'ssh-key', 'anonymous', 'user-password']; exports.DEFAULT_READ_STRATEGIES = DEFAULT_READ_STRATEGIES; class SSH { // Username entered by the user on the prompt user/pass process constructor({ path, username, port, host }) { (0, _defineProperty2().default)(this, "connection", void 0); (0, _defineProperty2().default)(this, "path", void 0); (0, _defineProperty2().default)(this, "username", void 0); (0, _defineProperty2().default)(this, "port", void 0); (0, _defineProperty2().default)(this, "host", void 0); (0, _defineProperty2().default)(this, "_sshUsername", void 0); this.path = path; this.username = username; this.port = port; this.host = host || ''; } /** * Network strategies: * 1) token (generated by bit-login command) * 2) ssh-agent (public-key should be saved on bit.dev, user needs to enable ssh-agent in its os. the agent saves the passphrase, so no need to enter) * 3) ssh-key. (user can specify location by `bit config`, if not, the default one is used. doesn't support passphrase) * 4) anonymous. (for read operations only) - trying to do the action as anonymous user * 5) prompt of user/password */ connect(strategiesNames = DEFAULT_STRATEGIES) { var _this = this; return (0, _bluebird().coroutine)(function* () { const strategies = { token: _this._tokenAuthentication, anonymous: _this._anonymousAuthentication, 'ssh-agent': _this._sshAgentAuthentication, 'ssh-key': _this._sshKeyAuthentication, 'user-password': _this._userPassAuthentication }; const strategiesFailures = []; for (const strategyName of strategiesNames) { _logger().default.debug(`ssh, trying to connect using ${strategyName}`); const strategyFunc = strategies[strategyName].bind(_this); try { const strategyResult = yield strategyFunc(); // eslint-disable-line if (strategyResult) return strategyResult; } catch (err) { _logger().default.debug(`ssh, failed to connect using ${strategyName}. ${err.message}`); if (err instanceof AuthenticationStrategyFailed) { strategiesFailures.push(err.message); } else { throw err; } } } _logger().default.errorAndAddBreadCrumb('ssh', 'all connection strategies have been failed!'); strategiesFailures.unshift('The following strategies were failed'); throw new (_exceptions().AuthenticationFailed)(strategiesFailures.join('\n[-] ')); })(); } _tokenAuthentication() { var _this2 = this; return (0, _bluebird().coroutine)(function* () { const sshConfig = _this2._composeTokenAuthObject(); if (!sshConfig) { throw new AuthenticationStrategyFailed('user token not defined in bit-config. please run `bit login` to authenticate.'); } const authFailedMsg = 'failed to authenticate with user token. generate a new token by running `bit logout && bit login`.'; return _this2._connectWithConfig(sshConfig, 'token', authFailedMsg); })(); } _anonymousAuthentication() { var _this3 = this; return (0, _bluebird().coroutine)(function* () { const sshConfig = _this3._composeAnonymousAuthObject(); if (!sshConfig) { throw new AuthenticationStrategyFailed('could not create the anonymous ssh configuration.'); } const authFailedMsg = 'collection might be private.'; return _this3._connectWithConfig(sshConfig, 'anonymous', authFailedMsg); })(); } _sshAgentAuthentication() { var _this4 = this; return (0, _bluebird().coroutine)(function* () { if (!_this4._hasAgentSocket()) { throw new AuthenticationStrategyFailed('unable to get SSH keys from ssh-agent to. perhaps service is down or disabled.'); } const sshConfig = (0, _lodash().default)(_this4._composeBaseObject(), { agent: process.env.SSH_AUTH_SOCK }); const authFailedMsg = 'no matching private key found in ssh-agent to authenticate to remote server.'; return _this4._connectWithConfig(sshConfig, 'ssh-agent', authFailedMsg); })(); } _sshKeyAuthentication() { var _this5 = this; return (0, _bluebird().coroutine)(function* () { const keyBuffer = yield (0, _keyGetter().default)(); if (!keyBuffer) { throw new AuthenticationStrategyFailed('SSH key not found in `~/.ssh/id_rsa` or `ssh_key_file` config in `bit config` either not configured or refers to wrong path.'); } const sshConfig = (0, _lodash().default)(_this5._composeBaseObject(), { privateKey: keyBuffer }); const authFailedMsg = 'failed connecting to remote server using `~/.ssh/id_rsa` or `ssh_key_file` in `bit config`.'; return _this5._connectWithConfig(sshConfig, 'ssh-key', authFailedMsg); })(); } _userPassAuthentication() { var _this6 = this; return (0, _bluebird().coroutine)(function* () { const sshConfig = yield _this6._composeUserPassObject(); const authFailedMsg = 'unable to connect using provided username and password combination.'; return _this6._connectWithConfig(sshConfig, 'user-password', authFailedMsg); })(); } close() { this.connection.end(); return this; } _composeBaseObject(passphrase) { return { username: this.username, host: this.host, port: this.port, passphrase, readyTimeout: _constants().DEFAULT_SSH_READY_TIMEOUT }; } _composeTokenAuthObject() { const processToken = _globalFlags().default.token; const token = processToken || (0, globalConfig().getSync)(_constants().CFG_USER_TOKEN_KEY); if (token) { this._sshUsername = 'token'; return (0, _lodash().default)(this._composeBaseObject(), { username: 'token', password: token }); } return null; } _composeAnonymousAuthObject() { this._sshUsername = 'anonymous'; return (0, _lodash().default)(this._composeBaseObject(), { username: 'anonymous', password: '' }); } _composeUserPassObject() { // @ts-ignore return (0, _prompts().userpass)().then(({ username, password }) => { _analytics().Analytics.setExtraData('authentication_method', 'user_password'); this._sshUsername = username; return (0, _lodash().default)(this._composeBaseObject(), { username, password }); }); } _hasAgentSocket() { return !!process.env.SSH_AUTH_SOCK; } _connectWithConfig(sshConfig, authenticationType, authFailedMsg) { var _this7 = this; return (0, _bluebird().coroutine)(function* () { const connectWithConfigP = () => { const conn = new (_ssh().default)(); return new Promise((resolve, reject) => { conn.on('error', err => { reject(err); }).on('ready', () => { resolve(conn); }).connect(sshConfig); }); }; try { _this7.connection = yield connectWithConfigP(); _analytics().Analytics.setExtraData('authentication_method', authenticationType); _logger().default.debug(`ssh, authenticated successfully using ${authenticationType}`); return _this7; } catch (err) { if (err.message === AUTH_FAILED_MESSAGE) { throw new AuthenticationStrategyFailed(authFailedMsg); } _logger().default.error('ssh', err); if (err.code === 'ENOTFOUND') { throw new (_generalError().default)(`unable to find the SSH server. host: ${err.host}, port: ${err.port}. Original error message: ${err.message}`); } if (err.message === PASSPHRASE_POSSIBLY_MISSING_MESSAGE) { const macMojaveOs = process.platform === 'darwin' && os().release() === '18.2.0'; let passphrasePossiblyMissing = 'error connecting with private ssh key. in case passphrase is used, use ssh-agent.'; if (macMojaveOs) { passphrasePossiblyMissing += ' for macOS Mojave users, use `-m PEM` for `ssh-keygen` command to generate a valid SSH key'; } throw new AuthenticationStrategyFailed(passphrasePossiblyMissing); } throw new AuthenticationStrategyFailed(`${authFailedMsg} due to an error "${err.message}"`); } })(); } buildCmd(commandName, path, payload, context) { const compress = globalConfig().getSync(_constants().CFG_SSH_NO_COMPRESS) !== 'true'; return `bit ${commandName} ${(0, _utils().toBase64)(path)} ${(0, _utils().packCommand)((0, _utils().buildCommandMessage)(payload, context, compress), true, compress)}`; } exec(commandName, payload, context) { _logger().default.debug(`ssh: going to run a remote command ${commandName}, path: ${this.path}`); // Add the entered username to context if (this._sshUsername) { context = context || {}; context.sshUsername = this._sshUsername; } // eslint-disable-next-line consistent-return return new Promise((resolve, reject) => { let res = ''; let err; // No need to use packCommand on the payload in case of put command // because we handle all the base64 stuff in a better way inside the ComponentObjects.manyToString // inside pushMany function here const cmd = this.buildCmd(commandName, absolutePath(this.path || ''), commandName === '_put' ? null : payload, context); if (!this.connection) { err = 'ssh connection is not defined'; _logger().default.error('ssh', err); return reject(err); } // eslint-disable-next-line consistent-return this.connection.exec(cmd, (error, stream) => { if (error) { _logger().default.error('ssh, exec returns an error: ', error); return reject(error); } if (commandName === '_put') { stream.stdin.write(payload); stream.stdin.end(); } stream.on('data', response => { res += response.toString(); }).on('exit', code => { _logger().default.debug(`ssh: exit. Exit code: ${code}`); const promiseExit = () => { return code && code !== 0 ? reject(this.errorHandler(code, err)) : resolve(clean(res)); }; // sometimes the connection 'exit' before 'close' and then it doesn't have the data (err) ready yet. // in that case, we prefer to wait until the onClose will terminate the promise. // sometimes though, the connection only 'exit' and never 'close' (happened when _put command sent back // more than 1MB of data), in that case, the following setTimeout will terminate the promise. setTimeout(promiseExit, 2000); }).on('close', (code, signal) => { if (commandName === '_put') res = res.replace(payload, ''); _logger().default.debug(`ssh: returned with code: ${code}, signal: ${signal}.`); // DO NOT CLOSE THE CONNECTION (using this.connection.end()), it causes bugs when there are several open // connections. Same bugs occur when running "this.connection.end()" on "end" or "exit" events. return code && code !== 0 ? reject(this.errorHandler(code, err)) : resolve(clean(res)); }).stderr.on('data', response => { err = response.toString(); _logger().default.error(`ssh: got an error, ${err}`); }); }); }); } // eslint-disable-next-line complexity errorHandler(code, err) { let parsedError; try { const { headers, payload } = this._unpack(err, false); checkVersionCompatibility(headers.version); parsedError = payload; } catch (e) { // be graceful when can't parse error message _logger().default.error(`ssh: failed parsing error as JSON, error: ${err}`); } switch (code) { default: return new (_exceptions().UnexpectedNetworkError)(parsedError ? parsedError.message : err); case 127: return new (_componentNotFound().default)(parsedError && parsedError.id || err); case 128: return new (_exceptions().PermissionDenied)(`${this.host}:${this.path}`); case 129: return new (_exceptions().RemoteScopeNotFound)(parsedError && parsedError.name || err); case 130: return new (_exceptions().PermissionDenied)(`${this.host}:${this.path}`); case 131: return new (_mergeConflictOnRemote().default)(parsedError && parsedError.idsAndVersions ? parsedError.idsAndVersions : []); case 132: return new (_customError().default)(parsedError && parsedError.message ? parsedError.message : err); case 133: return new (_exceptions().OldClientVersion)(parsedError && parsedError.message ? parsedError.message : err); case 134: { const msg = parsedError && parsedError.message ? parsedError.message : err; const sourceScope = parsedError && parsedError.sourceScope ? parsedError.sourceScope : 'unknown'; const destinationScope = parsedError && parsedError.destinationScope ? parsedError.destinationScope : 'unknown'; return new (_exportAnotherOwnerPrivate().default)(msg, sourceScope, destinationScope); } } } _unpack(data, base64 = true) { try { const unpacked = (0, _utils().unpackCommand)(data, base64); return unpacked; } catch (err) { _logger().default.error(`unpackCommand found on error "${err}", while parsing the following string: ${data}`); throw new (_exceptions().SSHInvalidResponse)(data); } } pushMany(manyComponentObjects, context) { // This ComponentObjects.manyToString will handle all the base64 stuff so we won't send this payload // to the pack command (to prevent duplicate base64) return this.exec('_put', _componentObjects().default.manyToString(manyComponentObjects), context).then(data => { const { payload, headers } = this._unpack(data); checkVersionCompatibility(headers.version); return payload.ids; }); } deleteMany(ids, force, context) { return this.exec('_delete', { bitIds: ids, force }, context).then(data => { const { payload } = this._unpack(data); return _removedComponents().default.fromObjects(payload); }); } deprecateMany(ids, context) { return this.exec('_deprecate', { ids }, context).then(data => { const { payload } = this._unpack(data); return payload; }); } undeprecateMany(ids, context) { return this.exec('_undeprecate', { ids }, context).then(data => { const { payload } = this._unpack(data); return payload; }); } push(componentObjects) { return this.pushMany([componentObjects]); } describeScope() { return this.exec('_scope').then(data => { const { payload, headers } = this._unpack(data); checkVersionCompatibility(headers.version); return payload; }).catch(() => { throw new (_exceptions().RemoteScopeNotFound)(this.path); }); } list(namespacesUsingWildcards) { var _this8 = this; return (0, _bluebird().coroutine)(function* () { return _this8.exec('_list', namespacesUsingWildcards).then( /*#__PURE__*/function () { var _ref = (0, _bluebird().coroutine)(function* (str) { const { payload, headers } = _this8._unpack(str); checkVersionCompatibility(headers.version); payload.forEach(result => { result.id = new (_bitId().BitId)(result.id); }); return payload; }); return function (_x) { return _ref.apply(this, arguments); }; }()); })(); } latestVersions(componentIds) { const componentIdsStr = componentIds.map(componentId => componentId.toString()); return this.exec('_latest', componentIdsStr).then(str => { const { payload, headers } = this._unpack(str); checkVersionCompatibility(headers.version); return payload; }); } search(query, reindex) { return this.exec('_search', { query, reindex: reindex.toString() }).then(data => { const { payload, headers } = this._unpack(data); checkVersionCompatibility(headers.version); return payload; }); } show(id) { return this.exec('_show', id.toString()).then(str => { const { payload, headers } = this._unpack(str); checkVersionCompatibility(headers.version); return str ? _component().default.fromString(payload) : null; }); } log(id) { return this.exec('_log', id.toString()).then(str => { const { payload, headers } = this._unpack(str); checkVersionCompatibility(headers.version); return str ? JSON.parse(payload) : null; }); } graph(bitId) { const idStr = bitId ? bitId.toString() : ''; return this.exec('_graph', idStr).then(str => { const { payload, headers } = this._unpack(str); checkVersionCompatibility(headers.version); return _scopeGraph().default.loadFromString(payload); }); } fetch(ids, noDeps = false, context) { var _this9 = this; return (0, _bluebird().coroutine)(function* () { let options = ''; const idsStr = ids.serialize(); if (noDeps) options = '--no-dependencies'; return _this9.exec(`_fetch ${options}`, idsStr, context).then(str => { const parseResponse = () => { try { const results = JSON.parse(str); return results; } catch (err) { throw new (_exceptions().SSHInvalidResponse)(str); } }; const { payload, headers } = parseResponse(); checkVersionCompatibility(headers.version); const componentObjects = _componentObjects().default.manyFromString(payload); return componentObjects; }); })(); } } exports.default = SSH;