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
JavaScript
"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;