firmament-yargs
Version:
Typescript classes for building CLI node applications
364 lines • 16.2 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
var SpawnImpl_1;
const inversify_1 = require("inversify");
const __1 = require("..");
const force_error_impl_1 = require("./force-error-impl");
const async = require("async");
const fs = require("fs");
const readlineSync = require('readline-sync');
const inpathSync = require('inpath').sync;
const psTree = require('ps-tree');
let SpawnImpl = SpawnImpl_1 = class SpawnImpl extends force_error_impl_1.ForceErrorImpl {
constructor(commandUtil, safeJson, positive, childProcessSpawn) {
super();
this.commandUtil = commandUtil;
this.safeJson = safeJson;
this.positive = positive;
this.childProcessSpawn = childProcessSpawn;
}
installAptitudePackages(packageNames, withInteractiveConfirm, cb) {
const me = this;
if (me.positive.areYouSure(`Looks like 'sshpass' is not installed. Want me to try to install it (using apt-get)? [Y/n]`, 'Operation canceled.', true, __1.FailureRetVal.TRUE)) {
}
me.sudoSpawnAsync([
'apt-get',
'install',
'-y',
'sshpass'
], {
suppressDiagnostics: true,
suppressStdOut: false,
suppressStdErr: false,
cacheStdOut: false,
cacheStdErr: false,
sudoPassword: 'password',
suppressResult: true,
}, () => {
}, (err, result) => {
cb(err, err ? err.message : `'sshpass' installed. Try operation again.`);
});
}
removeAptitudePackages(packageNames, withInteractiveConfirm, cb) {
}
validate_spawnShellCommandAsync_args(cmd, options, cbStatus, cbFinal, cbDiagnostic = null) {
const me = this;
cmd = cmd || [];
cmd = cmd.slice(0);
options = options || {};
options.preSpawnMessage = options.preSpawnMessage || '';
options.postSpawnMessage = options.postSpawnMessage || '';
options.sudoUser = options.sudoUser || '';
options.sudoPassword = options.sudoPassword || '';
options.stdio = options.stdio || 'pipe';
options.cwd = options.cwd || process.cwd();
cbStatus = me.checkCallback(cbStatus);
cbFinal = me.checkCallback(cbFinal);
cbDiagnostic = cbDiagnostic || (() => {
});
return { cmd, options, cbStatus, cbFinal, cbDiagnostic };
}
spawnShellCommandAsync(_cmd, _options, _cbStatus, _cbFinal, _cbDiagnostic = null) {
const me = this;
let { cmd, options, cbStatus, cbFinal, cbDiagnostic } = me.validate_spawnShellCommandAsync_args(_cmd, _options, _cbStatus, _cbFinal, _cbDiagnostic);
if (me.checkForceError('spawnShellCommandAsync', cbFinal)) {
return null;
}
if (!options.remoteHost && !options.remoteUser && !(options.remotePassword || options.remoteSshKeyPath)) {
return me._spawnShellCommandAsync(cmd, options, cbStatus, cbFinal, cbDiagnostic);
}
const msgBase = 'Not enough params for remote call:\n\n';
let msg = msgBase;
if (!options.remoteHost) {
msg += `* 'remoteHost' must be set to the remote server hostname\n`;
}
if (!options.remoteUser) {
msg += `* 'remoteUser' must be set to the user on the remote host to execute the call as\n`;
}
if (!(options.remotePassword || options.remoteSshKeyPath)) {
msg += `* either 'remotePassword' or 'remoteSshKeyPath' (or both, SshKey wins) must be specified`;
}
if (msg.length != msgBase.length) {
cbFinal(new Error(msg), msg);
return null;
}
options.remotePassword = options.remoteSshKeyPath ? undefined : options.remotePassword;
const tmp = require('tmp');
tmp.file({ discardDescriptor: true }, (err, tmpSrcPath) => {
if (err) {
return cbFinal(err, 'Failed to create temporary file');
}
const { remoteHost, remoteUser, remotePassword, remoteSshKeyPath, remoteSshPort } = options;
const subShellOptions = {
suppressDiagnostics: true,
suppressStdOut: true,
suppressStdErr: true,
cacheStdOut: true,
cacheStdErr: true,
suppressResult: false,
};
const envBashCmd = [
'/usr/bin/env',
'bash',
'-c'
];
const sshpassCmd = [
'sshpass',
'-p',
remotePassword
];
const sshKeyPathOptions = [
'-i',
`${remoteSshKeyPath}`
];
const sshOptions = [
'-q',
'-o',
'StrictHostKeyChecking=no'
];
const scpCmd = [
'scp',
...sshOptions
];
const sshCmd = [
'ssh',
...sshOptions
];
if (remoteSshPort) {
scpCmd.push('-P', `${remoteSshPort}`);
sshCmd.push('-p', `${remoteSshPort}`);
}
const finalScpCmd = remoteSshKeyPath ? scpCmd.concat(sshKeyPathOptions) : sshpassCmd.concat(scpCmd);
const finalSshCmd = remoteSshKeyPath ? sshCmd.concat(sshKeyPathOptions) : sshpassCmd.concat(sshCmd);
async.waterfall([
(cb) => {
const tmpDstPath = `${tmp.tmpNameSync()}.tmp`;
const writeStream = fs.createWriteStream(tmpSrcPath);
writeStream.on('close', () => {
cb(null, tmpDstPath);
});
const cmdString = cmd.join(' ') + `\nrm ${tmpDstPath}`;
writeStream.write(cmdString, () => {
writeStream.close();
});
},
(tmpDstPath, cb) => {
const cmd = finalScpCmd.concat([
tmpSrcPath,
`${remoteUser}@${remoteHost}:${tmpDstPath}`
]);
me._spawnShellCommandAsync(cmd, subShellOptions, () => {
}, (err, result) => {
cb(null, tmpDstPath, result);
});
},
(tmpDstPath, result, cb) => {
const remoteScriptCmd = `/usr/bin/env bash ${tmpDstPath}`;
const executeRemoteScriptCmd = remoteSshKeyPath
? remoteScriptCmd
: `echo ${remotePassword} | sudo -S ${remoteScriptCmd}`;
const cmd = finalSshCmd.concat(`${remoteUser}@${remoteHost} "${executeRemoteScriptCmd}"`);
const _cmd = envBashCmd.concat(cmd.join(' '));
me._spawnShellCommandAsync(_cmd, subShellOptions, (err, result) => {
me.commandUtil.log(result);
}, cb);
}
], (outerErr, result) => {
if (outerErr) {
me.safeJson.safeParse(outerErr.message, (err, obj) => {
try {
switch (typeof obj.code) {
case ('object'):
switch (obj.code.code) {
case ('ENOENT'):
me.commandUtil.processExitWithError(new Error(`Need to install 'sshpass': sudo apt-get install -y sshpass`));
break;
default:
return cbFinal(outerErr, outerErr.message);
}
break;
case ('number'):
return cbFinal(outerErr, obj.stderrText);
default:
return cbFinal(outerErr, outerErr.message);
}
}
catch (err) {
cbFinal(err, `Original Error: ${outerErr.message}`);
}
});
}
else {
cbFinal(null, result);
}
});
});
}
_spawnShellCommandAsync(cmd, options, cbStatus, cbFinal, cbDiagnostic = null) {
const me = this;
let childProcess;
try {
if (options.forceNullChildProcess) {
throw new Error('error: forceNullChildProcess');
}
let command = cmd[0];
let args = cmd.slice(1);
let stdoutText = '';
let stderrText = '';
if (!options.suppressDiagnostics) {
cbDiagnostic(`Running '${cmd}' @ '${options.cwd}'`);
options.preSpawnMessage && cbDiagnostic(options.preSpawnMessage);
}
childProcess = me.childProcessSpawn.spawn(command, args, options);
childProcess.stderr.on('data', (dataChunk) => {
if (options.suppressStdErr && !options.cacheStdErr) {
return;
}
const text = dataChunk.toString();
!options.suppressStdErr && cbStatus(new Error(text), text);
options.cacheStdErr && (stderrText += text);
});
childProcess.stdout.on('data', (dataChunk) => {
if (options.suppressStdOut && !options.cacheStdOut) {
return;
}
const text = dataChunk.toString();
!options.suppressStdOut && cbStatus(null, text);
options.cacheStdOut && (stdoutText += text);
});
childProcess.on('error', (code, signal) => {
cbFinal = SpawnImpl_1.childCloseOrExit(code || 10, signal || '', stdoutText, stderrText, options, cbFinal, cbDiagnostic);
});
childProcess.on('exit', (code, signal) => {
setTimeout(() => {
cbFinal = SpawnImpl_1.childCloseOrExit(code, signal || '', stdoutText, stderrText, options, cbFinal, cbDiagnostic);
}, 1000);
});
childProcess.on('close', (code, signal) => {
cbFinal = SpawnImpl_1.childCloseOrExit(code, signal || '', stdoutText, stderrText, options, cbFinal, cbDiagnostic);
});
}
catch (err) {
cbFinal && cbFinal(err, null);
}
return childProcess;
}
static childCloseOrExit(code, signal, stdoutText, stderrText, options, cbFinal, cbDiagnostic) {
if (cbFinal) {
!options.suppressDiagnostics && options.postSpawnMessage && cbDiagnostic(options.postSpawnMessage);
const returnString = JSON.stringify({ code, signal, stdoutText, stderrText }, undefined, 2);
const error = (code !== null && code !== 0)
? new Error(returnString)
: null;
cbFinal(options.suppressFinalError ? null : error, options.suppressResult ? '' : returnString);
}
return null;
}
sudoSpawnAsync(_cmd, _options, _cbStatus, _cbFinal, _cbDiagnostic = null) {
const me = this;
let { cmd, options, cbStatus, cbFinal, cbDiagnostic } = me.validate_spawnShellCommandAsync_args(_cmd, _options, _cbStatus, _cbFinal, _cbDiagnostic);
if (me.checkForceError('sudoSpawnAsync', cbFinal)) {
return;
}
const prompt = '#login-prompt#';
const args = ['-S', '-p', prompt];
if (options.sudoUser) {
args.unshift(`--user=${options.sudoUser}`);
}
[].push.apply(args, cmd);
const path = process.env['PATH'].split(':');
const sudoBin = inpathSync('sudo', path);
args.unshift(sudoBin);
const childProcess = me._spawnShellCommandAsync(args, options, (err, result) => {
if (err) {
try {
const lines = result.toString().trim().split('\n');
for (let i = 0; i < lines.length; ++i) {
if (lines[i] === prompt) {
return;
}
}
}
catch (err) {
}
}
cbStatus(err, result);
}, (err, result) => {
if (result) {
const regex = new RegExp(prompt, 'g');
result = result.replace(regex, '');
}
cbFinal(err, result);
}, cbDiagnostic);
if (!childProcess) {
return;
}
function waitForStartup(err, children) {
if (err) {
throw new Error(`Error spawning process`);
}
if (children && children.length) {
childProcess.stderr.removeAllListeners();
}
else {
setTimeout(function () {
psTree(childProcess.pid, waitForStartup);
}, 100);
}
}
psTree(childProcess.pid, waitForStartup);
let prompts = 0;
childProcess.stderr.on('data', function (data) {
const lines = data.toString().trim().split('\n');
lines.forEach(function (line) {
if (line === prompt) {
if (++prompts > 1) {
me.cachedPassword = null;
}
const username = require('username').sync();
if (!me.cachedPassword) {
if (options.sudoPassword) {
me.cachedPassword = options.sudoPassword;
}
else {
try {
const loginMessage = (prompts > 1)
? `Sorry, try again.\n[sudo] password for ${username}: `
: `[sudo] password for ${username}: `;
me.cachedPassword = readlineSync.question(loginMessage, { hideEchoBack: true });
}
catch (err) {
childProcess.kill();
return;
}
}
}
childProcess.stdin.write(me.cachedPassword + '\n');
}
});
});
return childProcess;
}
};
SpawnImpl = SpawnImpl_1 = __decorate([
inversify_1.injectable(),
__param(0, inversify_1.inject('CommandUtil')),
__param(1, inversify_1.inject('SafeJson')),
__param(2, inversify_1.inject('Positive')),
__param(3, inversify_1.inject('ChildProcessSpawn')),
__metadata("design:paramtypes", [Object, Object, Object, Object])
], SpawnImpl);
exports.SpawnImpl = SpawnImpl;
//# sourceMappingURL=spawn-impl.js.map