Skip to content

Commit

Permalink
[CopyFilesOverSSH] Migrated to ssh2-sftp-client library
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Dobrodeev committed Jul 28, 2020
1 parent e108198 commit c924676
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@
"loc.input.label.targetFolder": "Target folder",
"loc.input.help.targetFolder": "Target folder on the remote machine to where files will be copied. Example: /home/user/MySite.",
"loc.input.label.isWindowsOnTarget": "Target machine running Windows",
"loc.input.help.isWindowsOnTarget": "Indicates that remote machine is running Windows",
"loc.input.help.isWindowsOnTarget": "Target machine running Windows",
"loc.input.label.cleanTargetFolder": "Clean target folder",
"loc.input.help.cleanTargetFolder": "Delete all existing files and subfolders in the target folder before copying.",
"loc.input.label.readyTimeout": "SSH handshake timeout",
"loc.input.help.readyTimeout": "How long (in milliseconds) to wait for the SSH handshake to complete.",
"loc.input.label.overwrite": "Overwrite",
"loc.input.help.overwrite": "Replace existing files in and beneath the target folder.",
"loc.input.label.failOnEmptySource": "Fail if no files found to copy",
"loc.input.help.failOnEmptySource": "Fail if no matching files to be copied are found under the source folder.",
"loc.input.label.flattenFolders": "Flatten folders",
"loc.input.help.flattenFolders": "Flatten the folder structure and copy all files into the specified target folder on the remote machine.",
"loc.input.label.readyTimeout": "SSH handshake timeout",
"loc.input.help.readyTimeout": "How long (in milliseconds) to wait for the SSH handshake to complete.",
"loc.messages.CheckLogForStdErr": "Check the build log for STDERR from the command.",
"loc.messages.CleanTargetFolder": "Cleaning target folder %s on the remote machine",
"loc.messages.CleanTargetFolderFailed": "Failed to clean the target folder on the remote machine. %s",
Expand All @@ -41,5 +41,6 @@
"loc.messages.SourceNotFolder": "Source folder has to be a valid folder path.",
"loc.messages.StartedFileCopy": "Copying file %s to %s on remote machine.",
"loc.messages.UploadFileFailed": "Failed to upload %s to %s on remote machine. %s.",
"loc.messages.UseDefaultPort": "Using port 22 which is the default for SSH since no port was specified."
"loc.messages.UseDefaultPort": "Using port 22 which is the default for SSH since no port was specified.",
"loc.messages.TargetNotCreated": "Unable to create target folder %s."
}
2 changes: 1 addition & 1 deletion Tasks/CopyFilesOverSSHV0/ThirdPartyNotice.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This Azure DevOps extension (CopyFilesOverSSHV0) is based on or incorporates mat
18. path-is-absolute (git+https://github.com/sindresorhus/path-is-absolute.git)
19. q (git://github.com/kriskowal/q.git)
20. readable-stream (git://github.com/isaacs/readable-stream.git)
21. scp2 (git+https://github.com/lepture/node-scp2.git)
21. ssh2-sftp-client (git+https://https://github.com/theophilusx/ssh2-sftp-client.git)
22. semver (git+https://github.com/npm/node-semver.git)
23. shelljs (git://github.com/arturadib/shelljs.git)
24. ssh2 (git+ssh://git@github.com/mscdex/ssh2.git)
Expand Down
8 changes: 6 additions & 2 deletions Tasks/CopyFilesOverSSHV0/copyfilesoverssh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,11 @@ async function run() {
.replace(/^\//g, "");
}
tl.debug('relativePath = ' + relativePath);
const targetPath = path.posix.join(targetFolder, relativePath);
let targetPath = path.posix.join(targetFolder, relativePath);

if (!path.isAbsolute(targetPath)) {
targetPath = './' + targetPath
}

console.log(tl.loc('StartedFileCopy', fileToCopy, targetPath));
if (!overwrite) {
Expand Down Expand Up @@ -250,7 +254,7 @@ async function run() {
// close the client connection to halt build execution
if (sshHelper) {
tl.debug('Closing the client connection');
sshHelper.closeConnection();
await sshHelper.closeConnection();
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Tasks/CopyFilesOverSSHV0/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"homepage": "https://github.com/Microsoft.com/vsts-tasks#readme",
"dependencies": {
"scp2": "^0.5.0",
"ssh2-sftp-client": "^5.1.2",
"ssh2": "^0.8.2",
"minimatch": "^3.0.4",
"azure-pipelines-task-lib": "^2.9.3"
Expand Down
94 changes: 47 additions & 47 deletions Tasks/CopyFilesOverSSHV0/sshhelper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Q = require('q');
import tl = require('azure-pipelines-task-lib/task');
const path = require('path');
var Ssh2Client = require('ssh2').Client;
var Scp2Client = require('scp2').Client;
var SftpClient = require('ssh2-sftp-client');

export class RemoteCommandOptions {
public failOnStdErr : boolean;
Expand Down Expand Up @@ -39,18 +40,16 @@ export class SshHelper {
await defer.promise;
}

private async setupScpConnection() : Promise<void> {
private async setupSftpConnection() : Promise<void> {
const defer = Q.defer<void>();
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;
}

Expand All @@ -61,7 +60,7 @@ export class SshHelper {
console.log(tl.loc('SettingUpSSHConnection', this.sshConfig.host));
try {
await this.setupSshClientConnection();
await this.setupScpConnection();
await this.setupSftpConnection();
} catch(err) {
throw new Error(tl.loc('ConnectionFailed', err));
}
Expand All @@ -70,13 +69,10 @@ 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) {
Expand All @@ -93,18 +89,6 @@ export class SshHelper {
} 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);
}
}

/**
Expand All @@ -113,19 +97,37 @@ export class SshHelper {
* @param dest, folders will be created if they do not exist on remote server
* @returns {Promise<string>}
*/
uploadFile(sourceFile: string, dest: string) : Q.Promise<string> {
async uploadFile(sourceFile: string, dest: string) : Promise<string> {
if (process.platform === 'win32') {
dest = dest.replace(/\\/g, '/');
}

tl.debug('Upload ' + sourceFile + ' to ' + dest + ' on remote machine.');

var defer = Q.defer<string>();
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));

const remotePath = path.dirname(dest);
try {
if (!await this.sftpClient.exists(remotePath)) {
await this.sftpClient.mkdir(remotePath, true);
}
} catch (error) {
defer.reject(tl.loc('TargetNotCreated', remotePath));
}

try {
if (this.sshConfig.useFastPut) {
await this.sftpClient.fastPut(sourceFile, dest);
} else {
defer.resolve(dest);
await this.sftpClient.put(sourceFile, dest);
}
})
defer.resolve(dest);
} catch (err) {
defer.reject(tl.loc('UploadFileFailed', sourceFile, dest, err));
}
return defer.promise;
}

Expand All @@ -134,21 +136,19 @@ export class SshHelper {
* @param path
* @returns {Promise<boolean>}
*/
checkRemotePathExists(path: string) : Q.Promise<boolean> {
async checkRemotePathExists(path: string) : Promise<boolean> {
var defer = Q.defer<boolean>();

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;
}
Expand Down
9 changes: 5 additions & 4 deletions Tasks/CopyFilesOverSSHV0/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"author": "Microsoft Corporation",
"version": {
"Major": 0,
"Minor": 172,
"Patch": 3
"Minor": 173,
"Patch": 0
},
"demands": [],
"minimumAgentVersion": "2.144.0",
Expand Down Expand Up @@ -123,7 +123,7 @@
}
],
"execution": {
"Node": {
"Node10": {
"target": "copyfilesoverssh.js",
"argumentFormat": ""
}
Expand All @@ -146,6 +146,7 @@
"SourceNotFolder": "Source folder has to be a valid folder path.",
"StartedFileCopy": "Copying file %s to %s on remote machine.",
"UploadFileFailed": "Failed to upload %s to %s on remote machine. %s.",
"UseDefaultPort": "Using port 22 which is the default for SSH since no port was specified."
"UseDefaultPort": "Using port 22 which is the default for SSH since no port was specified.",
"TargetNotCreated": "Unable to create target folder %s."
}
}
9 changes: 5 additions & 4 deletions Tasks/CopyFilesOverSSHV0/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"author": "Microsoft Corporation",
"version": {
"Major": 0,
"Minor": 172,
"Patch": 3
"Minor": 173,
"Patch": 0
},
"demands": [],
"minimumAgentVersion": "2.144.0",
Expand Down Expand Up @@ -123,7 +123,7 @@
}
],
"execution": {
"Node": {
"Node10": {
"target": "copyfilesoverssh.js",
"argumentFormat": ""
}
Expand All @@ -146,6 +146,7 @@
"SourceNotFolder": "ms-resource:loc.messages.SourceNotFolder",
"StartedFileCopy": "ms-resource:loc.messages.StartedFileCopy",
"UploadFileFailed": "ms-resource:loc.messages.UploadFileFailed",
"UseDefaultPort": "ms-resource:loc.messages.UseDefaultPort"
"UseDefaultPort": "ms-resource:loc.messages.UseDefaultPort",
"TargetNotCreated": "ms-resource:loc.messages.TargetNotCreated"
}
}

0 comments on commit c924676

Please sign in to comment.