diff --git a/docs/early-access/v3/Extenders.md b/docs/early-access/v3/Extenders.md index 96e3c9c6f3..cf5769f0d2 100644 --- a/docs/early-access/v3/Extenders.md +++ b/docs/early-access/v3/Extenders.md @@ -14,3 +14,8 @@ - `ZoweVsCodeExtension.showVsCodeMessage` removed in favor of `Gui.showMessage`. - `ZoweVsCodeExtension.inputBox` removed in favor of `Gui.showInputBox`. - `ZoweVsCodeExtension.promptCredentials` removed in favor of `ZoweVsCodeExtension.updateCredentials`. + +## New APIs Added + +- `ICommand.issueUnixCommand` added for issuing Unix Commands +- Optional `ICommand.sshProfileRequired` API returning a boolean value for extenders that would like to use the ssh profile for issuing UNIX commands via Zowe Explorer. diff --git a/packages/zowe-explorer/__tests__/__unit__/command/unixCommandHandler.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/command/unixCommandHandler.unit.test.ts index ee429ae92b..ffef6ed8ac 100644 --- a/packages/zowe-explorer/__tests__/__unit__/command/unixCommandHandler.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/command/unixCommandHandler.unit.test.ts @@ -24,7 +24,6 @@ import { ZoweLogger } from "../../../src/utils/LoggerUtils"; import { SshSession } from "@zowe/zos-uss-for-zowe-sdk"; import { ZoweLocalStorage } from "../../../src/utils/ZoweLocalStorage"; import { ProfileManagement } from "../../../src/utils/ProfileManagement"; -import { profile } from "console"; describe("UnixCommand Actions Unit Testing", () => { const showQuickPick = jest.fn(); @@ -37,6 +36,7 @@ describe("UnixCommand Actions Unit Testing", () => { const mockLoadNamedProfile = jest.fn(); const mockdefaultProfile = jest.fn(); const getConfiguration = jest.fn(); + const getProfileInfomock = jest.fn(); const session = new imperative.Session({ user: "fake", @@ -128,6 +128,7 @@ describe("UnixCommand Actions Unit Testing", () => { Object.defineProperty(vscode.window, "createQuickPick", { value: createQuickPick }); Object.defineProperty(vscode.window, "createOutputChannel", { value: createOutputChannel }); Object.defineProperty(vscode.workspace, "getConfiguration", { value: getConfiguration }); + Object.defineProperty(imperative.ProfileInfo, "profAttrsToProfLoaded", { value: () => ({ profile: {} }) }); Object.defineProperty(imperative.ConnectionPropsForSessCfg, "addPropsOrPrompt", { value: jest.fn(() => { return { privateKey: undefined, keyPassphrase: undefined, handshakeTimeout: undefined, type: "basic", port: 22 }; @@ -165,6 +166,20 @@ describe("UnixCommand Actions Unit Testing", () => { return { privateKey: undefined, keyPassphrase: undefined, handshakeTimeout: undefined }; }); + getProfileInfomock.mockReturnValue({ + usingTeamConfig: true, + getAllProfiles: jest.fn().mockReturnValue(["dummy"]), + mergeArgsForProfile: jest.fn().mockReturnValue({ + knownArgs: [ + { argName: "port", argValue: "TEST", secure: false }, + { argName: "host", argValue: "TEST", secure: false }, + { argName: "user", secure: true }, + { argName: "password", secure: true }, + ], + }), + loadSecureArg: jest.fn().mockReturnValue("fake"), + } as any); + it("test the issueUnixCommand function", async () => { Object.defineProperty(profileLoader.Profiles, "getInstance", { value: jest.fn(() => { @@ -179,6 +194,7 @@ describe("UnixCommand Actions Unit Testing", () => { getBaseProfile: jest.fn(), getDefaultProfile: mockdefaultProfile, validProfile: ValidProfileEnum.VALID, + getProfileInfo: getProfileInfomock, }; }), }); @@ -232,6 +248,7 @@ describe("UnixCommand Actions Unit Testing", () => { getBaseProfile: jest.fn(), getDefaultProfile: mockdefaultProfile, validProfile: ValidProfileEnum.VALID, + getProfileInfo: getProfileInfomock, }; }), }); @@ -284,6 +301,7 @@ describe("UnixCommand Actions Unit Testing", () => { getBaseProfile: jest.fn(), getDefaultProfile: mockdefaultProfile, validProfile: ValidProfileEnum.VALID, + getProfileInfo: getProfileInfomock, }; }), }); @@ -329,6 +347,7 @@ describe("UnixCommand Actions Unit Testing", () => { getBaseProfile: jest.fn(), getDefaultProfile: mockdefaultProfile, validProfile: ValidProfileEnum.VALID, + getProfileInfo: getProfileInfomock, }; }), }); @@ -371,6 +390,7 @@ describe("UnixCommand Actions Unit Testing", () => { getBaseProfile: jest.fn(), getDefaultProfile: mockdefaultProfile, validProfile: ValidProfileEnum.VALID, + getProfileInfo: getProfileInfomock, }; }), }); @@ -397,6 +417,7 @@ describe("UnixCommand Actions Unit Testing", () => { getBaseProfile: jest.fn(), getDefaultProfile: mockdefaultProfile, validProfile: ValidProfileEnum.VALID, + getProfileInfo: getProfileInfomock, }; }), }); @@ -422,6 +443,7 @@ describe("UnixCommand Actions Unit Testing", () => { getBaseProfile: jest.fn(), getDefaultProfile: mockdefaultProfile, validProfile: ValidProfileEnum.VALID, + getProfileInfo: getProfileInfomock, }; }), }); @@ -465,6 +487,7 @@ describe("UnixCommand Actions Unit Testing", () => { getBaseProfile: jest.fn(), validProfile: ValidProfileEnum.VALID, getDefaultProfile: mockdefaultProfile, + getProfileInfo: getProfileInfomock, }; }), }); @@ -521,6 +544,7 @@ describe("UnixCommand Actions Unit Testing", () => { }), validProfile: ValidProfileEnum.INVALID, getDefaultProfile: mockdefaultProfile, + getProfileInfo: getProfileInfomock, }; }), }); @@ -589,6 +613,7 @@ describe("UnixCommand Actions Unit Testing", () => { checkCurrentProfile: jest.fn(), zosmfProfile: mockLoadNamedProfile, getDefaultProfile: mockdefaultProfile, + getProfileInfo: getProfileInfomock, }; }), }); @@ -609,12 +634,13 @@ describe("UnixCommand Actions Unit Testing", () => { }); it("ssh profile not found", async () => { - mockdefaultProfile.mockReset(); - mockdefaultProfile.mockReturnValueOnce(undefined); Object.defineProperty(profileLoader.Profiles, "getInstance", { value: jest.fn(() => { return { - getDefaultProfile: mockdefaultProfile, + getProfileInfo: jest.fn().mockReturnValue({ + usingTeamConfig: true, + getAllProfiles: jest.fn().mockReturnValue(undefined), + } as any), }; }), }); @@ -622,4 +648,58 @@ describe("UnixCommand Actions Unit Testing", () => { expect(showErrorMessage.mock.calls.length).toBe(1); expect(showErrorMessage.mock.calls[0][0]).toEqual("No SSH profile found. Please create an SSH profile before issuing Unix commands."); }); + + it("tests the selectSshProfile function", async () => { + showQuickPick.mockReturnValueOnce("test1" as any); + await expect( + (unixActions as any).selectSshProfile([ + { + name: "test1", + }, + { + name: "test2", + }, + ]) + ).resolves.toEqual({ + name: "test1", + }); + }); + + it("tests the selectSshProfile function when user escapes", async () => { + showQuickPick.mockReturnValueOnce(undefined); + await expect( + (unixActions as any).selectSshProfile([ + { + name: "test1", + }, + { + name: "test2", + }, + ]) + ).resolves.toBe(undefined); + expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation Cancelled"); + }); + it("ssh profile doesn't contain credentials", async () => { + Object.defineProperty(profileLoader.Profiles, "getInstance", { + value: jest.fn(() => { + return { + getProfileInfo: getProfileInfomock.mockReturnValue({ + usingTeamConfig: true, + getAllProfiles: jest.fn().mockReturnValue(["dummy"]), + mergeArgsForProfile: jest.fn().mockReturnValue({ + knownArgs: [ + { argName: "port", argValue: "TEST", secure: false }, + { argName: "host", argValue: "TEST", secure: false }, + { argName: "user", secure: true }, + { argName: "password", secure: true }, + ], + }), + loadSecureArg: jest.fn().mockReturnValue(undefined), + } as any) + } + }) + }); + await (unixActions as any).getSshProfile(); + expect(showErrorMessage.mock.calls[0][0]).toEqual("Credentials are missing for SSH profile"); + }) }); diff --git a/packages/zowe-explorer/__tests__/__unit__/command/unixCommandHandlerTheia.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/command/unixCommandHandlerTheia.unit.test.ts index 728cebd7a6..65a3ad811e 100644 --- a/packages/zowe-explorer/__tests__/__unit__/command/unixCommandHandlerTheia.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/command/unixCommandHandlerTheia.unit.test.ts @@ -106,6 +106,7 @@ describe("unixCommandActions unit testing", () => { return { privateKey: undefined, keyPassphrase: undefined, handshakeTimeout: undefined, type: "basic", port: 22 }; }), }); + Object.defineProperty(imperative.ProfileInfo, "profAttrsToProfLoaded", { value: () => ({ profile: {} }) }); Object.defineProperty(ProfileManagement, "getRegisteredProfileNameList", { value: jest.fn().mockReturnValue(["firstName", "secondName"]), configurable: true, @@ -147,6 +148,19 @@ describe("unixCommandActions unit testing", () => { getBaseProfile: jest.fn(), getDefaultProfile: mockdefaultProfile, validProfile: ValidProfileEnum.VALID, + getProfileInfo: jest.fn().mockReturnValue({ + usingTeamConfig: true, + getAllProfiles: jest.fn().mockReturnValue(["dummy"]), + mergeArgsForProfile: jest.fn().mockReturnValue({ + knownArgs: [ + { argName: "port", argValue: "TEST", secure: false }, + { argName: "host", argValue: "TEST", secure: false }, + { argName: "user", argValue: "TEST", secure: true }, + { argName: "password", argValue: "TEST", secure: true }, + ], + }), + loadSecureArg: jest.fn().mockReturnValue("user"), + } as any), }; }), }); diff --git a/packages/zowe-explorer/i18n/sample/src/command/UnixCommandHandler.i18n.json b/packages/zowe-explorer/i18n/sample/src/command/UnixCommandHandler.i18n.json index 5af7c281ef..794c60ab49 100644 --- a/packages/zowe-explorer/i18n/sample/src/command/UnixCommandHandler.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/command/UnixCommandHandler.i18n.json @@ -2,13 +2,15 @@ "issueUnixCommand.outputchannel.title": "Zowe Unix Command", "issueUnixCommand.quickPickOption": "Select the Profile to use to submit the Unix command", "issueUnixCommand.cancelled": "Operation Cancelled", - "issueUnixCommand.apiNonExisting": "Not implemented yet for profile of type: ", "unixCommand.HomeDirectory": "Redirecting to Home Directory", "issueUnixCommand.checkProfile": "Profile is invalid", "issueUnixCommand.noProfilesLoaded": "No profiles available", "path.notselected": "Enter a UNIX file filter search to enable Issue Unix Command from the tree view.", "issueUnixCommand.options.nopathentered": "Operation cancelled.", + "issueUnixCommand.apiNonExisting": "Not implemented yet for profile of type: ", + "issueUnixCommand.sshProfile.quickPickOption": "Select the ssh Profile.", "setsshProfile.couldnotfindprofile": "No SSH profile found. Please create an SSH profile before issuing Unix commands.", + "sshcredentialsMissing": "Credentials are missing for SSH profile", "issueUnixCommand.command.hostname": "Select a Unix command to run against ", "issueUnixCommand.command.edit": " (An option to edit will follow)", "issueUnixCommand.options.noselection": "No selection made. Operation cancelled.", diff --git a/packages/zowe-explorer/src/command/UnixCommandHandler.ts b/packages/zowe-explorer/src/command/UnixCommandHandler.ts index 9d31b91be1..a1d176817e 100644 --- a/packages/zowe-explorer/src/command/UnixCommandHandler.ts +++ b/packages/zowe-explorer/src/command/UnixCommandHandler.ts @@ -89,25 +89,31 @@ export class UnixCommandHandler extends ZoweCommandProvider { } } } + let res: boolean = true; if (!session) { + const allProfiles = profiles.allProfiles; + res = this.checkForSshRequired(allProfiles); const profileNamesList = ProfileManagement.getRegisteredProfileNameList(globals.Trees.USS); if (profileNamesList.length) { - const quickPickOptions: vscode.QuickPickOptions = { - placeHolder: localize("issueUnixCommand.quickPickOption", "Select the Profile to use to submit the Unix command"), - ignoreFocusOut: true, - canPickMany: false, - }; - const sesName = await Gui.showQuickPick(profileNamesList, quickPickOptions); - if (sesName === undefined) { - Gui.showMessage(localize("issueUnixCommand.cancelled", "Operation Cancelled")); - return; + if (!res) { + const quickPickOptions: vscode.QuickPickOptions = { + placeHolder: localize("issueUnixCommand.quickPickOption", "Select the Profile to use to submit the Unix command"), + ignoreFocusOut: true, + canPickMany: false, + }; + const sesName = await Gui.showQuickPick(profileNamesList, quickPickOptions); + if (sesName === undefined) { + Gui.showMessage(localize("issueUnixCommand.cancelled", "Operation Cancelled")); + return; + } + profile = allProfiles.find((temprofile) => temprofile.name === sesName); } - const allProfiles = profiles.allProfiles; - profile = allProfiles.find((temprofile) => temprofile.name === sesName); - if (ZoweExplorerApiRegister.getCommandApi(profile).sshProfileRequired) sshRequiredBoolean = true; - if (!sshRequiredBoolean) { - Gui.showMessage(localize("issueUnixCommand.apiNonExisting", "Not implemented yet for profile of type: ") + profile.type); - return; + if (ZoweExplorerApiRegister.getCommandApi(profile).sshProfileRequired) { + sshRequiredBoolean = true; + this.sshSession = await this.setsshSession(); + if (!this.sshSession) { + return; + } } if (cwd == "") { cwd = await vscode.window.showInputBox({ @@ -135,14 +141,6 @@ export class UnixCommandHandler extends ZoweCommandProvider { } else { profile = node.getProfile(); } - if (ZoweExplorerApiRegister.getCommandApi(profile).sshProfileRequired) sshRequiredBoolean = true; - if (sshRequiredBoolean) { - this.sshSession = await this.setsshSession(); - if (!this.sshSession) return; - } else { - Gui.showMessage(localize("issueUnixCommand.apiNonExisting", "Not implemented yet for profile of type: ") + profile.type); - return; - } if (cwd == "" && this.flag) { Gui.errorMessage(localize("path.notselected", "Enter a UNIX file filter search to enable Issue Unix Command from the tree view.")); return; @@ -151,9 +149,19 @@ export class UnixCommandHandler extends ZoweCommandProvider { Gui.showMessage(localize("issueUnixCommand.options.nopathentered", "Operation cancelled.")); return; } + if (ZoweExplorerApiRegister.getCommandApi(profile).sshProfileRequired && sshRequiredBoolean == undefined) { + this.sshSession = await this.setsshSession(); + if (!this.sshSession) { + return; + } + } try { if (Profiles.getInstance().validProfile !== ValidProfileEnum.INVALID) { const commandApi = ZoweExplorerApiRegister.getInstance().getCommandApi(profile); + if (!ZoweExplorerApiRegister.getCommandApi(profile).issueUnixCommand) { + Gui.errorMessage(localize("issueUnixCommand.apiNonExisting", "Not implemented yet for profile of type: ") + profile.type); + return; + } if (commandApi) { let command1: string = command; if (!command) { @@ -175,21 +183,94 @@ export class UnixCommandHandler extends ZoweCommandProvider { } } + public checkForSshRequired(allProfiles: imperative.IProfileLoaded[]): boolean { + try { + allProfiles.forEach((p) => { + // eslint-disable-next-line @typescript-eslint/unbound-method + if (!ZoweExplorerApiRegister.getCommandApi(p).sshProfileRequired) { + return false; + } + }); + } catch (error) { + return false; + } + } + public async setsshSession(): Promise { ZoweLogger.trace("UnixCommandHandler.setsshSession called."); - const sshprofile: imperative.IProfileLoaded = Profiles.getInstance().getDefaultProfile("ssh"); - if (!sshprofile) { + const sshprofile: imperative.IProfileLoaded = await this.getSshProfile(); + if (sshprofile) { + const cmdArgs: imperative.ICommandArguments = this.getCmdArgs(sshprofile?.profile as imperative.IProfileLoaded); + // create the ssh session + const sshSessCfg = SshSession.createSshSessCfgFromArgs(cmdArgs); + imperative.ConnectionPropsForSessCfg.resolveSessCfgProps(sshSessCfg, cmdArgs); + this.sshSession = new SshSession(sshSessCfg); + } + return this.sshSession; + } + + private async selectSshProfile(sshProfiles: imperative.IProfileLoaded[] = []): Promise { + ZoweLogger.trace("UnixCommandHandler.selectSshProfile called."); + let sshProfile: imperative.IProfileLoaded; + if (sshProfiles.length > 1) { + const sshProfileNamesList = sshProfiles.map((temprofile) => { + return temprofile.name; + }); + if (sshProfileNamesList.length) { + const quickPickOptions: vscode.QuickPickOptions = { + placeHolder: localize("issueUnixCommand.sshProfile.quickPickOption", "Select the ssh Profile."), + ignoreFocusOut: true, + canPickMany: false, + }; + const sesName = await Gui.showQuickPick(sshProfileNamesList, quickPickOptions); + if (sesName === undefined) { + Gui.showMessage(localize("issueUnixCommand.cancelled", "Operation Cancelled")); + return; + } + + sshProfile = sshProfiles.filter((temprofile) => temprofile.name === sesName)[0]; + } + } else if (sshProfiles.length > 0) { + sshProfile = sshProfiles[0]; + } + return sshProfile; + } + + private async getSshProfile(): Promise { + ZoweLogger.trace("UnixCommandHandler.getsshParams called."); + const profileInfo = await Profiles.getInstance().getProfileInfo(); + const params = ["port", "host","user","password"]; + const profiles = profileInfo.getAllProfiles("ssh"); + if (!profiles) { Gui.errorMessage( localize("setsshProfile.couldnotfindprofile", "No SSH profile found. Please create an SSH profile before issuing Unix commands.") ); return; } - const cmdArgs: imperative.ICommandArguments = this.getCmdArgs(sshprofile?.profile as imperative.IProfileLoaded); - // create the ssh session - const sshSessCfg = SshSession.createSshSessCfgFromArgs(cmdArgs); - const sshSessCfgWithCreds = await imperative.ConnectionPropsForSessCfg.addPropsOrPrompt(sshSessCfg, cmdArgs); - this.sshSession = new SshSession(sshSessCfgWithCreds); - return this.sshSession; + let exitflag: boolean; + let sshProfile: imperative.IProfileLoaded; + if (profiles.length > 0) { + sshProfile = await this.selectSshProfile(profiles.map((p) => imperative.ProfileInfo.profAttrsToProfLoaded(p))); + if (sshProfile != null) { + const prof = profileInfo.mergeArgsForProfile(sshProfile.profile as imperative.IProfAttrs); + params.forEach((p) => { + const obj = prof.knownArgs.find((a) => a.argName === p); + if (obj) { + if (obj.argValue) { + sshProfile.profile[p] = obj.argValue; + } else { + sshProfile.profile[p] = profileInfo.loadSecureArg(obj); + if(!sshProfile.profile[p]){ + exitflag = true; + Gui.errorMessage(localize("sshcredentialsMissing", "Credentials are missing for SSH profile")); + } + } + } + }); + } + } + if(exitflag) return; + return sshProfile; } private async getQuickPick(cwd: string): Promise {