diff --git a/Tasks/CopyFilesOverSSHV0/copyfilesoverssh.ts b/Tasks/CopyFilesOverSSHV0/copyfilesoverssh.ts index 044b294a38ef..f9d238fbc09b 100644 --- a/Tasks/CopyFilesOverSSHV0/copyfilesoverssh.ts +++ b/Tasks/CopyFilesOverSSHV0/copyfilesoverssh.ts @@ -247,14 +247,14 @@ async function run() { // close the client connection to halt build execution if (sshHelper) { tl.debug('Closing the client connection'); - sshHelper.closeConnection(); + await sshHelper.closeConnection(); } } } run().then(() => { - tl.debug('Task successfully accomplished'); - }) + tl.debug('Task successfully accomplished'); +}) .catch(err => { tl.debug('Run was unexpectedly failed due to: ' + err); }); diff --git a/Tasks/CopyFilesOverSSHV0/package.json b/Tasks/CopyFilesOverSSHV0/package.json index 2a7c38b2dfd7..1905b242cccc 100644 --- a/Tasks/CopyFilesOverSSHV0/package.json +++ b/Tasks/CopyFilesOverSSHV0/package.json @@ -17,8 +17,7 @@ }, "homepage": "https://github.com/Microsoft.com/vsts-tasks#readme", "dependencies": { - "scp2": "^0.5.0", - "ssh2": "^0.8.2", + "ssh2-sftp-client": "^5.1.2", "minimatch": "^3.0.4", "azure-pipelines-task-lib": "^2.9.3" } diff --git a/Tasks/CopyFilesOverSSHV0/sshhelper.ts b/Tasks/CopyFilesOverSSHV0/sshhelper.ts index d0ec87cb46e6..49741501dbf5 100644 --- a/Tasks/CopyFilesOverSSHV0/sshhelper.ts +++ b/Tasks/CopyFilesOverSSHV0/sshhelper.ts @@ -1,18 +1,17 @@ import Q = require('q'); import tl = require('azure-pipelines-task-lib/task'); var Ssh2Client = require('ssh2').Client; -var Scp2Client = require('scp2').Client; +var SftpClient = require('ssh2-sftp-client'); export class RemoteCommandOptions { - public failOnStdErr : boolean; + public failOnStdErr: boolean; } export class SshHelper { private sshConfig: any; private sshClient: any; - private scpClient: any; private sftpClient: any; - + /** * Constructor that takes a configuration object of format * { @@ -28,7 +27,7 @@ export class SshHelper { this.sshConfig = sshConfig; } - private async setupSshClientConnection() : Promise { + private async setupSshClientConnection(): Promise { const defer = Q.defer(); this.sshClient = new Ssh2Client(); this.sshClient.once('ready', () => { @@ -39,18 +38,16 @@ export class SshHelper { await defer.promise; } - private async setupScpConnection() : Promise { + private async setupSftpConnection(): Promise { const defer = Q.defer(); - this.scpClient = new Scp2Client(); - this.scpClient.defaults(this.sshConfig); - this.scpClient.sftp((err, sftp) => { - if(err) { - defer.reject(tl.loc('ConnectionFailed', err)); - } else { - this.sftpClient = sftp; - defer.resolve(); - } - }) + try { + this.sftpClient = new SftpClient(); + await this.sftpClient.connect(this.sshConfig) + defer.resolve(); + } catch (err) { + this.sftpClient = null; + defer.reject(tl.loc('ConnectionFailed', err)); + } await defer.promise; } @@ -61,8 +58,8 @@ export class SshHelper { console.log(tl.loc('SettingUpSSHConnection', this.sshConfig.host)); try { await this.setupSshClientConnection(); - await this.setupScpConnection(); - } catch(err) { + await this.setupSftpConnection(); + } catch (err) { throw new Error(tl.loc('ConnectionFailed', err)); } } @@ -70,16 +67,13 @@ export class SshHelper { /** * Close any open client connections for SSH, SCP and SFTP */ - closeConnection() { + async closeConnection() { try { if (this.sftpClient) { - this.sftpClient.on('error', (err) => { - tl.debug('sftpClient: Ignoring error diconnecting: ' + err); - }); // ignore logout errors; see: https://github.com/mscdex/node-imap/issues/695 - this.sftpClient.close(); + await this.sftpClient.end(); this.sftpClient = null; } - } catch(err) { + } catch (err) { tl.debug('Failed to close SFTP client: ' + err); } try { @@ -90,21 +84,10 @@ export class SshHelper { this.sshClient.end(); this.sshClient = null; } - } catch(err) { + } catch (err) { tl.debug('Failed to close SSH client: ' + err); } - try { - if (this.scpClient) { - this.scpClient.on('error', (err) => { - tl.debug('scpClient: Ignoring error diconnecting: ' + err); - }); // ignore logout errors; see: https://github.com/mscdex/node-imap/issues/695 - this.scpClient.close(); - this.scpClient = null; - } - } catch(err) { - tl.debug('Failed to close SCP client: ' + err); - } } /** @@ -113,19 +96,18 @@ export class SshHelper { * @param dest, folders will be created if they do not exist on remote server * @returns {Promise} */ - uploadFile(sourceFile: string, dest: string) : Q.Promise { + async uploadFile(sourceFile: string, dest: string): Promise { tl.debug('Upload ' + sourceFile + ' to ' + dest + ' on remote machine.'); var defer = Q.defer(); - if(!this.scpClient) { + if (!this.sftpClient) { defer.reject(tl.loc('ConnectionNotSetup')); } - this.scpClient.upload(sourceFile, dest, (err) => { - if(err) { - defer.reject(tl.loc('UploadFileFailed', sourceFile, dest, err)); - } else { - defer.resolve(dest); - } - }) + try { + await this.sftpClient.put(sourceFile, dest); + defer.resolve(dest); + } catch (err) { + defer.reject(tl.loc('UploadFileFailed', sourceFile, dest, err)); + } return defer.promise; } @@ -134,21 +116,20 @@ export class SshHelper { * @param path * @returns {Promise} */ - checkRemotePathExists(path: string) : Q.Promise { + async checkRemotePathExists(path: string): Promise { var defer = Q.defer(); - if(!this.sftpClient) { + if (!this.sftpClient) { defer.reject(tl.loc('ConnectionNotSetup')); } - this.sftpClient.stat(path, function(err, attr) { - if(err) { - //path does not exist - defer.resolve(false); - } else { - //path exists - defer.resolve(true); - } - }) + + if (await this.sftpClient.stat(path)) { + //path exists + defer.resolve(true); + } else { + //path does not exist + defer.resolve(false); + } return defer.promise; } @@ -159,41 +140,41 @@ export class SshHelper { * @param options * @returns {Promise} */ - runCommandOnRemoteMachine(command: string, options: RemoteCommandOptions) : Q.Promise { + runCommandOnRemoteMachine(command: string, options: RemoteCommandOptions): Q.Promise { var defer = Q.defer(); - var stdErrWritten:boolean = false; + var stdErrWritten: boolean = false; - if(!this.sshClient) { + if (!this.sshClient) { defer.reject(tl.loc('ConnectionNotSetup')); } - if(!options) { + if (!options) { tl.debug('Options not passed to runCommandOnRemoteMachine, setting defaults.'); var options = new RemoteCommandOptions(); options.failOnStdErr = true; } var cmdToRun = command; - if(cmdToRun.indexOf(';') > 0) { + if (cmdToRun.indexOf(';') > 0) { //multiple commands were passed separated by ; cmdToRun = cmdToRun.replace(/;/g, '\n'); } tl.debug('cmdToRun = ' + cmdToRun); this.sshClient.exec(cmdToRun, (err, stream) => { - if(err) { + if (err) { defer.reject(tl.loc('RemoteCmdExecutionErr', cmdToRun, err)) } stream.on('close', (code, signal) => { tl.debug('code = ' + code + ', signal = ' + signal); - if(code && code != 0) { + if (code && code != 0) { //non zero exit code - fail defer.reject(tl.loc('RemoteCmdNonZeroExitCode', cmdToRun, code)); } else { //no exit code or exit code of 0 //based on the options decide whether to fail the build or not if data was written to STDERR - if(stdErrWritten === true && options.failOnStdErr === true) { + if (stdErrWritten === true && options.failOnStdErr === true) { //stderr written - fail the build defer.reject(tl.loc('RemoteCmdExecutionErr', cmdToRun, tl.loc('CheckLogForStdErr'))); } else { @@ -204,12 +185,12 @@ export class SshHelper { }).on('data', (data) => { console.log(data); }).stderr.on('data', (data) => { - stdErrWritten = true; - tl.debug('stderr = ' + data); - if(data && data.toString().trim() !== '') { - tl.error(data); - } - }); + stdErrWritten = true; + tl.debug('stderr = ' + data); + if (data && data.toString().trim() !== '') { + tl.error(data); + } + }); }); return defer.promise; }