UNPKG

firmament-yargs

Version:

Typescript classes for building CLI node applications

364 lines 16.2 kB
"use strict"; 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