import { Client } from 'ssh2' import { isArray } from '@plastichub/core/primitives' import { CONFIG_DEFAULT } from '@plastichub/osr-cli-commons' import { logger } from '../src' import { option } from './' const parseConnectionOptions = function (options: any) { const connectionOptions: any = { host: options.host, port: options.port, username: options.user, readyTimeout: options.readyTimeout, agentForward: options.agentForward } if (options.privateKey) { connectionOptions.privateKey = options.privateKey.trim(); if (options.passphrase) { connectionOptions.passphrase = options.passphrase.trim(); } } else if (options.password) { connectionOptions.password = options.password; if (options.agent) { connectionOptions.agent = options.agent; } } else { connectionOptions.agent = options.agent; } return connectionOptions } const exce = async (commands: string[], options: any) => { return new Promise((resolve, reject) => { const c = new Client(); const { debug, verbose } = options debug && logger.info('Raw Options', options) c.on('keyboard-interactive', function () { const prompts = arguments[3] const reply = arguments[4] prompts.forEach(function (question) { const msg = question.prompt.toLowerCase() if (msg.indexOf('password') !== -1) { reply([options.password]) } }) }) c.on('connect', () => verbose && logger.info('Connection :: connect')) c.on('ready', () => { verbose && logger.info('Connection :: ready') execCommand() }) c.on('error', (err) => verbose.error('Connection :: error :: ' + err)) c.on('end', () => verbose && logger.info('Connection :: end')) const execCommand = () => { if (commands.length === 0) { c.end() } else { const command = commands.shift() debug && logger.info('Executing :: ' + command) c.exec(command, options, function (err, stream) { if (err) { reject(err) } let out stream.on('data', (data, extended) => { out = String(data); if (extended === 'stderr') { if (!options.suppressRemoteErrors) { logger.warn(out); } else { logger.warn(out); } } else { logger.info(out) } }) stream.on('end', () => { verbose && logger.info('Stream :: EOF') resolve(out) }) stream.on('exit', () => debug && logger.info('Stream :: exit')) stream.on('close', (code, signal) => { debug && logger.info('Stream :: close :: code: ' + code + ', signal: ' + signal) if (!options.ignoreErrors && code !== 0) { logger.error('Error executing task ' + command); c.end() reject({ signal, code }) } else { execCommand() } }) }) } } const connectionOptions = parseConnectionOptions(options) connectionOptions.tryKeyboard = true; const proxyHost = (options.proxy || {}).host if (proxyHost) { const proxyConnectionOptions = parseConnectionOptions(options.proxy) const proxyConnection = new Client() proxyConnection.on('connect', function () { debug && logger.info('Proxy connection :: connect') }) proxyConnection.on('error', function (err) { debug && logger.error('Proxy connection :: error :: ' + err) }) proxyConnection.on('ready', function () { logger.info('Proxy connection :: ready') proxyConnection.exec('nc ' + connectionOptions.host + ' ' + connectionOptions.port, function (err, stream) { if (err) { proxyConnection.end() reject(err) } connectionOptions.sock = stream; c.connect(connectionOptions) }); }); proxyConnection.connect(proxyConnectionOptions) } else { c.connect(connectionOptions) } }) } export const register = (grunt) => { grunt.registerMultiTask('sshexec', 'Runs commands on SSH', async function () { let done = this.async() const task_options = this.data || {} const sshConfig = (CONFIG_DEFAULT() as any || {}).ssh || {} const opts = { ...sshConfig, debug: option('debug', task_options, grunt), verbose: option('verbose', task_options, grunt), port: option('port', task_options, grunt, sshConfig.port), host: option('host', task_options, grunt, sshConfig.host), user: option('user', task_options, grunt, sshConfig.user), password: option('password', task_options, grunt, sshConfig.password) } opts.debug && logger.info('Git - task data ', this.data) let commands: string[] = isArray(this.data.commands) ? this.data.commands : [this.data.commands] const ret = await exce(commands, opts) opts.debug && logger.info(`Exce : ${[this.data.command].join('\n')} : `, ret) done() }) }