diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index f553ae64f..c564d3ae0 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to the Zowe CLI package will be documented in this file. +## Recent Changes + +- BugFix: Removed unnecessary `$ ` characters in front of most output. [zowe-explorer#3079(comment)](https://github.com/zowe/zowe-explorer-vscode/pull/3079#pullrequestreview-2408842655) ## `8.6.2` diff --git a/packages/zosuss/CHANGELOG.md b/packages/zosuss/CHANGELOG.md index 5ebdb64df..7c8438e3d 100644 --- a/packages/zosuss/CHANGELOG.md +++ b/packages/zosuss/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to the Zowe z/OS USS SDK package will be documented in this file. +## Recent Changes + +- BugFix: Removed unnecessary `$ ` characters in front of most output. [zowe-explorer#3079(comment)](https://github.com/zowe/zowe-explorer-vscode/pull/3079#pullrequestreview-2408842655) +- Enhancement: Added the ability to validate if an SSH profile can successfully establish a connection, ensuring quicker troubleshooting of connection issues. [zowe-explorer#3079(comment)](https://github.com/zowe/zowe-explorer-vscode/pull/3079#discussion_r1825783867) + ## `8.1.1` - BugFix: Updated peer dependencies to `^8.0.0`, dropping support for versions tagged `next`. [#2287](https://github.com/zowe/zowe-cli/pull/2287) diff --git a/packages/zosuss/__tests__/__system__/Shell.system.test.ts b/packages/zosuss/__tests__/__system__/Shell.system.test.ts index eece57bd5..936531fe0 100644 --- a/packages/zosuss/__tests__/__system__/Shell.system.test.ts +++ b/packages/zosuss/__tests__/__system__/Shell.system.test.ts @@ -35,6 +35,19 @@ describe("zowe uss issue ssh api call test", () => { await TestEnvironment.cleanUp(TEST_ENVIRONMENT); }); + describe("Function isConnectionValid", () => { + it("should verify that the connection is valid", async () => { + const response = await Shell.isConnectionValid(SSH_SESSION); + expect(response).toBe(true); + }); + it("should verify that the connection is invalid", async () => { + const fakeSession: SshSession = TestEnvironment.createSshSession(TEST_ENVIRONMENT); + fakeSession.ISshSession.hostname = "fake-host"; + const response = await Shell.isConnectionValid(fakeSession); + expect(response).toBe(false); + }); + }); + it ("should execute uname command on the remote system by ssh and return operating system name", async () => { const command = "uname"; let stdoutData = ""; @@ -149,7 +162,8 @@ describe("zowe uss issue ssh api call test", () => { expect(error.toString()).toContain(ZosUssMessages.connectionRefused.message); } else { expect(error.toString().includes(ZosUssMessages.allAuthMethodsFailed.message) || - error.toString().includes(ZosUssMessages.connectionRefused.message)).toBe(true); + error.toString().includes(ZosUssMessages.connectionRefused.message) || + error.toString().includes(ZosUssMessages.unexpected.message)).toBe(true); } }, TIME_OUT); diff --git a/packages/zosuss/__tests__/__unit__/Shell.unit.test.ts b/packages/zosuss/__tests__/__unit__/Shell.unit.test.ts index f8c3b397a..7ddb5058a 100644 --- a/packages/zosuss/__tests__/__unit__/Shell.unit.test.ts +++ b/packages/zosuss/__tests__/__unit__/Shell.unit.test.ts @@ -54,7 +54,7 @@ const mockShell = jest.fn().mockImplementation((callback) => { (Client as any).mockImplementation(() => { mockClient.connect = mockConnect; mockClient.shell = mockShell; - mockClient.end = jest.fn(); + mockClient.end = jest.fn().mockReturnValue(mockClient); return mockClient; }); @@ -103,6 +103,29 @@ describe("Shell", () => { checkMockFunctionsWithCommand(command); }); + it("Should execute ssh command with cwd option and no extra characters in the output", async () => { + const cwd = "/"; + const command = "commandtest"; + await Shell.executeSshCwd(fakeSshSession, command, cwd, stdoutHandler, true); + + checkMockFunctionsWithCommand(command); + }); + + describe("Connection validation", () => { + it("should determine that the connection is valid", async () => { + const response = await Shell.isConnectionValid(fakeSshSession); + expect(response).toBe(true); + }); + it("should determine that the connection is invalid", async () => { + mockConnect.mockImplementationOnce(() => { + mockClient.emit("error", new Error(Shell.connRefusedFlag)); + mockStream.emit("exit", 0); + }); + const response = await Shell.isConnectionValid(fakeSshSession); + expect(response).toBe(false); + }); + }); + describe("Error handling", () => { it("should fail when password is expired", async () => { mockShell.mockImplementationOnce((callback) => { diff --git a/packages/zosuss/src/Shell.ts b/packages/zosuss/src/Shell.ts index dc7ff271f..ed319fbbd 100644 --- a/packages/zosuss/src/Shell.ts +++ b/packages/zosuss/src/Shell.ts @@ -23,20 +23,11 @@ export class Shell { public static executeSsh(session: SshSession, command: string, - stdoutHandler: (data: string) => void): Promise { - const authsAllowed = ["none"]; + stdoutHandler: (data: string) => void, + removeExtraCharactersFromOutput = false): Promise { let hasAuthFailed = false; const promise = new Promise((resolve, reject) => { const conn = new Client(); - - // These are needed for authenticationHandler - // The order is critical as this is the order of authentication that will be used. - if (session.ISshSession.privateKey != null && session.ISshSession.privateKey !== "undefined") { - authsAllowed.push("publickey"); - } - if (session.ISshSession.password != null && session.ISshSession.password !== "undefined") { - authsAllowed.push("password"); - } conn.on("ready", () => { conn.shell((err: any, stream: ClientChannel) => { if (err) { throw err; } @@ -77,6 +68,7 @@ export class Shell { return; } dataBuffer += data; + if (dataBuffer.includes("\r")) { // when data is not received with complete lines, // slice the last incomplete line and put it back to dataBuffer until it gets the complete line, @@ -101,6 +93,8 @@ export class Shell { else if (isUserCommand && dataToPrint.length != 0) { if (!dataToPrint.startsWith('\r\n$ '+cmd) && !dataToPrint.startsWith('\r<')){ //only prints command output + if (removeExtraCharactersFromOutput && dataToPrint.startsWith("\r\n$ ")) + dataToPrint = dataToPrint.replace(/\r\n\$\s/, "\r\n"); stdoutHandler(dataToPrint); dataToPrint = ""; } @@ -140,28 +134,53 @@ export class Shell { })); } }); - conn.connect({ - host: session.ISshSession.hostname, - port: session.ISshSession.port, - username: session.ISshSession.user, - password: session.ISshSession.password, - privateKey: session.ISshSession.privateKey != null && session.ISshSession.privateKey !== "undefined" ? - require("fs").readFileSync(session.ISshSession.privateKey) : "", - passphrase: session.ISshSession.keyPassphrase, - authHandler: this.authenticationHandler(authsAllowed), - readyTimeout: session.ISshSession.handshakeTimeout != null && session.ISshSession.handshakeTimeout !== undefined ? - session.ISshSession.handshakeTimeout : 0 - } as any); + Shell.connect(conn, session); }); return promise; } + private static connect(connection: Client, session: SshSession) { + const authsAllowed = ["none"]; + + // These are needed for authenticationHandler + // The order is critical as this is the order of authentication that will be used. + if (session.ISshSession.privateKey != null && session.ISshSession.privateKey !== "undefined") { + authsAllowed.push("publickey"); + } + if (session.ISshSession.password != null && session.ISshSession.password !== "undefined") { + authsAllowed.push("password"); + } + + connection.connect({ + host: session.ISshSession.hostname, + port: session.ISshSession.port, + username: session.ISshSession.user, + password: session.ISshSession.password, + privateKey: session.ISshSession.privateKey != null && session.ISshSession.privateKey !== "undefined" ? + require("fs").readFileSync(session.ISshSession.privateKey) : "", + passphrase: session.ISshSession.keyPassphrase, + authHandler: this.authenticationHandler(authsAllowed), + readyTimeout: session.ISshSession.handshakeTimeout != null && session.ISshSession.handshakeTimeout !== undefined ? + session.ISshSession.handshakeTimeout : 0 + } as any); + } + public static async executeSshCwd(session: SshSession, command: string, cwd: string, - stdoutHandler: (data: string) => void): Promise { + stdoutHandler: (data: string) => void, + removeExtraCharactersFromOutput = false + ): Promise { const cwdCommand = `cd ${cwd} && ${command}`; - return this.executeSsh(session, cwdCommand, stdoutHandler); + return this.executeSsh(session, cwdCommand, stdoutHandler, removeExtraCharactersFromOutput); + } + + public static async isConnectionValid(session: SshSession): Promise{ + return new Promise((resolve, _) => { + const conn = new Client(); + conn.on("ready", () => conn.end() && resolve(true)).on("error", () => resolve(false)); + Shell.connect(conn, session); + }); } private static authenticationHandler(authsAllowed: string[]) {