From 0d558686f061c60e22f63967bfeb4149335c9e9a Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 7 Mar 2024 10:44:18 -0500 Subject: [PATCH 01/16] Add SEP port configuration (#1893) Signed-off-by: worksofliam --- package.json | 5 +++++ src/api/Configuration.ts | 2 ++ src/webviews/settings/index.ts | 1 + 3 files changed, 8 insertions(+) diff --git a/package.json b/package.json index c13075c90..87040edd1 100644 --- a/package.json +++ b/package.json @@ -379,6 +379,11 @@ "default": "8005", "description": "Port to connect to IBM i Debug Service." }, + "debugSepPort": { + "type": "string", + "default": "8008", + "description": "Port to connect to IBM i Debug Service for SEP." + }, "debugIsSecure": { "type": "boolean", "default": false, diff --git a/src/api/Configuration.ts b/src/api/Configuration.ts index fcf2c8e93..ea2749c23 100644 --- a/src/api/Configuration.ts +++ b/src/api/Configuration.ts @@ -54,6 +54,7 @@ export namespace ConnectionConfiguration { showDescInLibList: boolean; debugCertDirectory: string; debugPort: string; + debugSepPort: string; debugIsSecure: boolean; debugUpdateProductionFiles: boolean; debugEnableDebugTracing: boolean; @@ -136,6 +137,7 @@ export namespace ConnectionConfiguration { showDescInLibList: (parameters.showDescInLibList === true), debugCertDirectory: (parameters.debugCertDirectory || "/QIBM/ProdData/IBMiDebugService/bin/certs"), debugPort: (parameters.debugPort || "8005"), + debugSepPort: (parameters.debugSepPort || "8008"), debugIsSecure: (parameters.debugIsSecure === true), debugUpdateProductionFiles: (parameters.debugUpdateProductionFiles === true), debugEnableDebugTracing: (parameters.debugEnableDebugTracing === true), diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts index d9dffd232..771102711 100644 --- a/src/webviews/settings/index.ts +++ b/src/webviews/settings/index.ts @@ -174,6 +174,7 @@ export class SettingsUI { if (connection && connection.remoteFeatures[`startDebugService.sh`]) { debuggerTab .addInput(`debugPort`, `Debug port`, `Default secure port is 8005. Tells the client which port the debug service is running on.`, { default: config.debugPort, minlength: 1, maxlength: 5, regexTest: `^\\d+$` }) + .addInput(`debugPort`, `SEP debug port`, `Default secure port is 8008. Tells the client which port the debug service for SEP is running on.`, { default: config.debugSepPort, minlength: 1, maxlength: 5, regexTest: `^\\d+$` }) .addCheckbox(`debugUpdateProductionFiles`, `Update production files`, `Determines whether the job being debugged can update objects in production (*PROD) libraries.`, config.debugUpdateProductionFiles) .addCheckbox(`debugEnableDebugTracing`, `Debug trace`, `Tells the debug service to send more data to the client. Only useful for debugging issues in the service. Not recommended for general debugging.`, config.debugEnableDebugTracing); From 3d73bf1b1265b6b7605b2ebabbc92b4768059d2c Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 7 Mar 2024 10:58:38 -0500 Subject: [PATCH 02/16] Change Java versions based on service version (#1841) Signed-off-by: worksofliam --- src/api/debug/index.ts | 4 +-- src/api/debug/server.ts | 58 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/api/debug/index.ts b/src/api/debug/index.ts index 99b40c835..aa260cf3e 100644 --- a/src/api/debug/index.ts +++ b/src/api/debug/index.ts @@ -346,7 +346,7 @@ export async function initialize(context: ExtensionContext) { if (confirmEndServer === `End service`) { progress.report({ increment: 33, message: `Ending currently running service.` }); try { - await server.end(connection); + await server.end(instance); startupService = true; } catch (e: any) { vscode.window.showErrorMessage(`Failed to end existing debug service (${e.message})`); @@ -359,7 +359,7 @@ export async function initialize(context: ExtensionContext) { if (startupService) { progress.report({ increment: 34, message: `Starting service up.` }); try { - await server.startup(connection); + await server.startup(instance); } catch (e: any) { vscode.window.showErrorMessage(`Failed to start debug service (${e.message})`); } diff --git a/src/api/debug/server.ts b/src/api/debug/server.ts index 2f4c8bd8c..f572309d5 100644 --- a/src/api/debug/server.ts +++ b/src/api/debug/server.ts @@ -4,15 +4,55 @@ import { window } from "vscode"; import IBMi from "../IBMi"; import IBMiContent from "../IBMiContent"; import * as certificates from "./certificates"; +import Instance from "../Instance"; const directory = `/QIBM/ProdData/IBMiDebugService/bin/`; -const MY_JAVA_HOME = `MY_JAVA_HOME="/QOpenSys/QIBM/ProdData/JavaVM/jdk80/64bit"`; +const detailFile = `package,json`; -export async function startup(connection: IBMi) { +const JavaPaths: {[version: string]: string} = { + "8": `/QOpenSys/QIBM/ProdData/JavaVM/jdk80/64bit`, + "11": `/QOpenSys/QIBM/ProdData/JavaVM/jdk11/64bit` +} + +interface DebugServiceDetails { + version: string; + java: string; +} + +function getMyJavaHome(javaVersion: string) { + if (JavaPaths[javaVersion]) { + return `MY_JAVA_HOME="${JavaPaths[javaVersion]}"`; + } +} + +async function getDebugServiceDetails(content: IBMiContent): Promise { + const detailExists = await content.testStreamFile(path.posix.join(directory, detailFile), "r"); + if (detailExists) { + const fileContents = await content.downloadStreamfile(path.posix.join(directory, detailFile)); + try { + return JSON.parse(fileContents); + } catch (e) { + // Something very very bad has happened + console.log(e); + } + } + + return { + version: `1.0.0`, + java: `8` + } +} + +export async function startup(instance: Instance){ + const connection = instance.getConnection()!; + const content = instance.getContent()!; + const host = connection.currentHost; + const details = await getDebugServiceDetails(content); + const javaHome = getMyJavaHome(details.java); const encryptResult = await connection.sendCommand({ - command: `${MY_JAVA_HOME} DEBUG_SERVICE_KEYSTORE_PASSWORD="${host}" ${path.posix.join(directory, `encryptKeystorePassword.sh`)} | /usr/bin/tail -n 1` + command: `${javaHome} DEBUG_SERVICE_KEYSTORE_PASSWORD="${host}" ${path.posix.join(directory, `encryptKeystorePassword.sh`)} | /usr/bin/tail -n 1` }); if ((encryptResult.code || 0) >= 1) { @@ -28,7 +68,7 @@ export async function startup(connection: IBMi) { const keystorePath = certificates.getRemoteServerCertPath(connection); connection.sendCommand({ - command: `${MY_JAVA_HOME} DEBUG_SERVICE_KEYSTORE_PASSWORD="${password}" DEBUG_SERVICE_KEYSTORE_FILE="${keystorePath}" /QOpenSys/usr/bin/nohup "${path.posix.join(directory, `startDebugService.sh`)}"` + command: `${javaHome} DEBUG_SERVICE_KEYSTORE_PASSWORD="${password}" DEBUG_SERVICE_KEYSTORE_FILE="${keystorePath}" /QOpenSys/usr/bin/nohup "${path.posix.join(directory, `startDebugService.sh`)}"` }).then(startResult => { if ((startResult.code || 0) >= 1) { window.showErrorMessage(startResult.stdout || startResult.stderr); @@ -56,9 +96,15 @@ export async function getRunningJob(localPort: string, content: IBMiContent): Pr return (rows.length > 0 ? String(rows[0].JOB_NAME) : undefined); } -export async function end(connection: IBMi): Promise { +export async function end(instance: Instance): Promise { + const connection = instance.getConnection()!; + const content = instance.getContent()!; + + const details = await getDebugServiceDetails(content); + const javaHome = getMyJavaHome(details.java); + const endResult = await connection.sendCommand({ - command: `${MY_JAVA_HOME} ${path.posix.join(directory, `stopDebugService.sh`)}` + command: `${javaHome} ${path.posix.join(directory, `stopDebugService.sh`)}` }); if (endResult.code && endResult.code >= 0) { From 1caac599c1b7b9da610249f19eeaff7a91d65f78 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 7 Mar 2024 11:01:02 -0500 Subject: [PATCH 03/16] Pass in port from environment variable on startup (#1894) Signed-off-by: worksofliam --- src/api/debug/server.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/api/debug/server.ts b/src/api/debug/server.ts index f572309d5..5ef4ad926 100644 --- a/src/api/debug/server.ts +++ b/src/api/debug/server.ts @@ -46,13 +46,14 @@ async function getDebugServiceDetails(content: IBMiContent): Promise= 1) { From d0f24faec83ebaf1f7e8505c795eceeeef236c31 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 7 Mar 2024 11:03:20 -0500 Subject: [PATCH 04/16] New batch subtype for debugging (#1823) Signed-off-by: worksofliam --- src/api/debug/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/debug/index.ts b/src/api/debug/index.ts index aa260cf3e..385480a37 100644 --- a/src/api/debug/index.ts +++ b/src/api/debug/index.ts @@ -504,6 +504,7 @@ export async function startDebug(instance: Instance, options: DebugOptions) { "port": port, "secure": secure, // Enforce secure mode "ignoreCertificateErrors": !secure, + "subType": "batch", "library": options.library.toUpperCase(), "program": options.object.toUpperCase(), "startBatchJobCommand": `SBMJOB CMD(${currentCommand}) INLLIBL(${options.libraries.libraryList.join(` `)}) CURLIB(${options.libraries.currentLibrary}) JOBQ(QSYSNOMAX) MSGQ(*USRPRF)`, From 70eaaf217ea17acbf45e46a5a8a989123e644ab1 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 7 Mar 2024 11:28:20 -0500 Subject: [PATCH 05/16] Start of work to support SEP debugging (#1823) Signed-off-by: worksofliam --- src/api/debug/index.ts | 129 ++++++++++++++++++++++++---------------- src/api/debug/server.ts | 19 ++++-- 2 files changed, 91 insertions(+), 57 deletions(-) diff --git a/src/api/debug/index.ts b/src/api/debug/index.ts index 385480a37..d5c3b5de4 100644 --- a/src/api/debug/index.ts +++ b/src/api/debug/index.ts @@ -421,12 +421,11 @@ export async function initialize(context: ExtensionContext) { } } else { const existingDebugService = await server.getRunningJob(connection.config?.debugPort || "8005", instance.getContent()!); - - const openTut = await vscode.window.showInformationMessage(`${ - existingDebugService ? - `Looks like the Debug Service was started by an external service. This may impact your VS Code experience.` : - `Looks like you have the debug PTF but don't have it configured.` - } Do you want to see the Walkthrough to set it up?`, `Take me there`); + + const openTut = await vscode.window.showInformationMessage(`${existingDebugService ? + `Looks like the Debug Service was started by an external service. This may impact your VS Code experience.` : + `Looks like you have the debug PTF but don't have it configured.` + } Do you want to see the Walkthrough to set it up?`, `Take me there`); if (openTut === `Take me there`) { vscode.commands.executeCommand(`workbench.action.openWalkthrough`, `halcyontechltd.vscode-ibmi-walkthroughs#code-ibmi-debug`); @@ -439,18 +438,23 @@ export async function initialize(context: ExtensionContext) { vscode.commands.executeCommand(`setContext`, `code-for-ibmi:debugManaged`, isManaged()); } -function validateIPv4address(ipaddress: string) { - if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress)) { - return (true) +function validateIPv4address(ipaddress: string) { + if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress)) { + return (true) } - return (false) -} + return (false) +} interface DebugOptions { password: string; library: string; object: string; - libraries: ILELibrarySettings + libraries: ILELibrarySettings; + sep?: { + type: "*PGM" | "*SRVPGM"; + moduleName?: string; + procedureName?: string; + } }; export async function startDebug(instance: Instance, options: DebugOptions) { @@ -458,6 +462,8 @@ export async function startDebug(instance: Instance, options: DebugOptions) { const config = instance.getConfig(); const storage = instance.getStorage(); + const serviceDetails = await server.getDebugServiceDetails(instance.getContent()!); + const port = config?.debugPort; const updateProductionFiles = config?.debugUpdateProductionFiles; const enableDebugTracing = config?.debugEnableDebugTracing; @@ -477,48 +483,69 @@ export async function startDebug(instance: Instance, options: DebugOptions) { } } - const pathKey = options.library.trim() + `/` + options.object.trim(); - - const previousCommands = storage!.getDebugCommands(); + if (options.sep) { + if (serviceDetails.version === `1.0.0`) { + vscode.window.showErrorMessage(`The debug service on this system, version ${serviceDetails.version}, does not support service entry points.`); + return; + } - let currentCommand: string | undefined = previousCommands[pathKey] || `CALL PGM(` + pathKey + `)`; + // libraryName/programName programType/moduleName/procedureName + const formattedDebugString = `${options.library.toUpperCase()}/${options.object.toUpperCase()} ${options.sep.type}/${options.sep.moduleName || `*ALL`}/${options.sep.procedureName || `*ALL`}`; + vscode.commands.executeCommand( + `ibmidebug.create-service-entry-point-with-prompt`, + connection?.currentHost!, + connection?.currentUser!.toUpperCase(), + options.password, + formattedDebugString, + Number(config?.debugPort), + Number(config?.debugSepPort) + ); - currentCommand = await vscode.window.showInputBox({ - ignoreFocusOut: true, - title: `Debug command`, - prompt: `Command used to start debugging the ${pathKey} program object. The command is wrapped around SBMJOB.`, - value: currentCommand - }); + } else { - if (currentCommand) { - previousCommands[pathKey] = currentCommand; - storage?.setDebugCommands(previousCommands); - - const debugConfig = { - "type": `IBMiDebug`, - "request": `launch`, - "name": `Remote debug: Launch a batch debug session`, - "user": connection!.currentUser.toUpperCase(), - "password": options.password, - "host": connection!.currentHost, - "port": port, - "secure": secure, // Enforce secure mode - "ignoreCertificateErrors": !secure, - "subType": "batch", - "library": options.library.toUpperCase(), - "program": options.object.toUpperCase(), - "startBatchJobCommand": `SBMJOB CMD(${currentCommand}) INLLIBL(${options.libraries.libraryList.join(` `)}) CURLIB(${options.libraries.currentLibrary}) JOBQ(QSYSNOMAX) MSGQ(*USRPRF)`, - "updateProductionFiles": updateProductionFiles, - "trace": enableDebugTracing, - }; - - const debugResult = await vscode.debug.startDebugging(undefined, debugConfig, undefined); - - if (debugResult) { - connectionConfirmed = true; - } else { - if (!connectionConfirmed) { - temporaryPassword = undefined; + const pathKey = options.library.trim() + `/` + options.object.trim(); + + const previousCommands = storage!.getDebugCommands(); + + let currentCommand: string | undefined = previousCommands[pathKey] || `CALL PGM(` + pathKey + `)`; + + currentCommand = await vscode.window.showInputBox({ + ignoreFocusOut: true, + title: `Debug command`, + prompt: `Command used to start debugging the ${pathKey} program object. The command is wrapped around SBMJOB.`, + value: currentCommand + }); + + if (currentCommand) { + previousCommands[pathKey] = currentCommand; + storage?.setDebugCommands(previousCommands); + + const debugConfig = { + "type": `IBMiDebug`, + "request": `launch`, + "name": `Remote debug: Launch a batch debug session`, + "user": connection!.currentUser.toUpperCase(), + "password": options.password, + "host": connection!.currentHost, + "port": port, + "secure": secure, // Enforce secure mode + "ignoreCertificateErrors": !secure, + "subType": "batch", + "library": options.library.toUpperCase(), + "program": options.object.toUpperCase(), + "startBatchJobCommand": `SBMJOB CMD(${currentCommand}) INLLIBL(${options.libraries.libraryList.join(` `)}) CURLIB(${options.libraries.currentLibrary}) JOBQ(QSYSNOMAX) MSGQ(*USRPRF)`, + "updateProductionFiles": updateProductionFiles, + "trace": enableDebugTracing, + }; + + const debugResult = await vscode.debug.startDebugging(undefined, debugConfig, undefined); + + if (debugResult) { + connectionConfirmed = true; + } else { + if (!connectionConfirmed) { + temporaryPassword = undefined; + } } } } diff --git a/src/api/debug/server.ts b/src/api/debug/server.ts index 5ef4ad926..c97f9ec9c 100644 --- a/src/api/debug/server.ts +++ b/src/api/debug/server.ts @@ -25,22 +25,29 @@ function getMyJavaHome(javaVersion: string) { } } -async function getDebugServiceDetails(content: IBMiContent): Promise { +let debugServiceDetails: DebugServiceDetails | undefined; +export async function getDebugServiceDetails(content: IBMiContent): Promise { + if (debugServiceDetails) { + return debugServiceDetails; + } + + debugServiceDetails = { + version: `1.0.0`, + java: `8` + }; + const detailExists = await content.testStreamFile(path.posix.join(directory, detailFile), "r"); if (detailExists) { const fileContents = await content.downloadStreamfile(path.posix.join(directory, detailFile)); try { - return JSON.parse(fileContents); + debugServiceDetails = JSON.parse(fileContents); } catch (e) { // Something very very bad has happened console.log(e); } } - return { - version: `1.0.0`, - java: `8` - } + return debugServiceDetails!; } export async function startup(instance: Instance){ From 4a9d869924f978a0772d5640c1b400251346f04c Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 7 Mar 2024 23:09:45 -0500 Subject: [PATCH 06/16] Logic to debug from active editor or object browser Signed-off-by: worksofliam --- src/api/debug/index.ts | 76 +++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/src/api/debug/index.ts b/src/api/debug/index.ts index d5c3b5de4..6d82693a9 100644 --- a/src/api/debug/index.ts +++ b/src/api/debug/index.ts @@ -10,6 +10,7 @@ import { copyFileSync } from "fs"; import { instance } from "../../instantiate"; import { getEnvConfig } from "../local/env"; import { ILELibrarySettings } from "../CompileTools"; +import { ObjectItem } from "../../typings"; const debugExtensionId = `IBM.ibmidebug`; @@ -31,7 +32,7 @@ export async function initialize(context: ExtensionContext) { return debugclient !== undefined; } - const startDebugging = async (objectLibrary: string, objectName: string, workspaceFolder?: vscode.WorkspaceFolder) => { + const startDebugging = async (type: DebugType, objectType: DebugObjectType, objectLibrary: string, objectName: string, workspaceFolder?: vscode.WorkspaceFolder) => { if (debugExtensionAvailable()) { const connection = instance.getConnection(); const config = instance.getConfig(); @@ -59,13 +60,19 @@ export async function initialize(context: ExtensionContext) { } if (password) { - const debugOpts: DebugOptions = { + let debugOpts: DebugOptions = { password, library: objectLibrary, object: objectName, libraries }; + if (type === `sep`) { + debugOpts.sep = { + type: objectType + } + } + startDebug(instance, debugOpts); } } else { @@ -93,7 +100,24 @@ export async function initialize(context: ExtensionContext) { } } - const getObjectFromUri = async (uri: Uri) => { + let cachedResolvedTypes: { [path: string]: DebugObjectType } = {}; + const getObjectType = async(library: string, objectName: string) => { + const path = library + `/` + objectName; + if (cachedResolvedTypes[path]) { + return cachedResolvedTypes[path]; + } else { + const content = instance.getContent()!; + + const [row] = await content.runSQL(`select OBJTYPE from table(qsys2.object_statistics('${library}', '*PGM *SRVPGM', '${objectName}')) X`) as { OBJTYPE: DebugObjectType }[]; + + if (row) { + cachedResolvedTypes[path] = row.OBJTYPE; + return row.OBJTYPE; + }; + } + } + + const getObjectFromUri = (uri: Uri) => { const connection = instance.getConnection(); const configuration = instance.getConfig(); @@ -187,24 +211,35 @@ export async function initialize(context: ExtensionContext) { } }), - vscode.commands.registerCommand(`code-for-ibmi.debug.activeEditor`, async () => { - const activeEditor = vscode.window.activeTextEditor; - if (activeEditor) { - // Get the workspace folder if one is available. - const workspaceFolder = [`member`, `streamfile`].includes(activeEditor.document.uri.scheme) ? undefined : vscode.workspace.getWorkspaceFolder(activeEditor.document.uri); - - const qualifiedObject = await getObjectFromUri(activeEditor.document.uri); + vscode.commands.registerCommand(`code-for-ibmi.debug.batch`, (node?: ObjectItem|Uri) => { + vscode.commands.executeCommand(`code-for-ibmi.debug`, `batch`, node); + }), - if (qualifiedObject.library && qualifiedObject.object) { - startDebugging(qualifiedObject.library, qualifiedObject.object, workspaceFolder); - } - } + vscode.commands.registerCommand(`code-for-ibmi.debug.sep`, (node?: ObjectItem|Uri) => { + vscode.commands.executeCommand(`code-for-ibmi.debug`, `sep`, node); }), - vscode.commands.registerCommand(`code-for-ibmi.debug.program`, async (node) => { - const [library, object] = node.path.split(`/`); - if (library && object) { - startDebugging(library, object); + vscode.commands.registerCommand(`code-for-ibmi.debug`, async (debugType?: DebugType, node?: ObjectItem|Uri) => { + if (debugType && node) { + if (node instanceof Uri) { + const workspaceFolder = [`member`, `streamfile`].includes(node.scheme) ? undefined : vscode.workspace.getWorkspaceFolder(node); + + const qualifiedObject = getObjectFromUri(node); + + if (qualifiedObject.library && qualifiedObject.object) { + const objectType = await getObjectType(qualifiedObject.library, qualifiedObject.object); + if (objectType) { + startDebugging(debugType, objectType, qualifiedObject.library, qualifiedObject.object, workspaceFolder); + } else { + vscode.window.showErrorMessage(`Failed to determine object type. Ensure the object exists and is a program (*PGM) or service program (*SRVPGM).`); + } + } + } else + if ('object' in node) { + const { library, name, type } = node.object + + startDebugging(debugType, type as DebugObjectType, library, name); + } } }), @@ -451,12 +486,15 @@ interface DebugOptions { object: string; libraries: ILELibrarySettings; sep?: { - type: "*PGM" | "*SRVPGM"; + type: DebugObjectType; moduleName?: string; procedureName?: string; } }; +type DebugType = "batch" | "sep"; +type DebugObjectType = "*PGM" | "*SRVPGM"; + export async function startDebug(instance: Instance, options: DebugOptions) { const connection = instance.getConnection(); const config = instance.getConfig(); From ae400c12a7cb2c69aec0e9cabd878f0049ace400 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 8 Mar 2024 10:04:25 -0500 Subject: [PATCH 07/16] Initial work on package JSON Signed-off-by: worksofliam --- package.json | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 87040edd1..a908f19e5 100644 --- a/package.json +++ b/package.json @@ -965,16 +965,17 @@ "enablement": "code-for-ibmi:connected && code-for-ibmi:debugManaged != true" }, { - "command": "code-for-ibmi.debug.activeEditor", - "title": "Start Debugging Active Source", + "command": "code-for-ibmi.debug.batch", + "title": "Start Debugging (Batch)", "category": "IBM i", "icon": "$(debug-alt)", "enablement": "code-for-ibmi:connected" }, { - "command": "code-for-ibmi.debug.program", - "title": "Debug Program", + "command": "code-for-ibmi.debug.sep", + "title": "Start Debugging (SEP)", "category": "IBM i", + "icon": "$(debug-alt)", "enablement": "code-for-ibmi:connected" }, { @@ -1764,6 +1765,10 @@ { "id": "code-for-ibmi.openMember", "label": "Open" + }, + { + "id": "code-for-ibmi.debug.group", + "label": "Start Debugging" } ], "menus": { @@ -1800,6 +1805,14 @@ "when": "viewItem == member" } ], + "code-for-ibmi.debug.group": [ + { + "command": "code-for-ibmi.debug.batch" + }, + { + "command": "code-for-ibmi.debug.sep" + } + ], "commandPalette": [ { "command": "code-for-ibmi.userLibraryList.enable", @@ -2062,7 +2075,11 @@ "when": "never" }, { - "command": "code-for-ibmi.debug.program", + "command": "code-for-ibmi.debug.batch", + "when": "never" + }, + { + "command": "code-for-ibmi.debug.sep", "when": "never" }, { @@ -2251,7 +2268,7 @@ ], "editor/title": [ { - "command": "code-for-ibmi.debug.activeEditor", + "submenu": "code-for-ibmi.debug.group", "when": "code-for-ibmi:connected && !inDebugMode && editorLangId =~ /^rpgle$|^rpg$|^cobol$|^cl$/i", "group": "navigation@1" }, @@ -2448,8 +2465,8 @@ "group": "1_workspace@1" }, { - "command": "code-for-ibmi.debug.program", - "when": "view == objectBrowser && !inDebugMode && viewItem =~ /^object.pgm.*/", + "command": "code-for-ibmi.debug.group", + "when": "view == objectBrowser && !inDebugMode && (viewItem =~ /^object.pgm.*/ || viewItem =~ /^object.srvpgm.*/)", "group": "2_debug@1" }, { From 69a14eb62860367edd1b745b9ee43cd945ab7f0d Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 8 Mar 2024 10:11:33 -0500 Subject: [PATCH 08/16] Fix labels and missing icon Signed-off-by: worksofliam --- package.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a908f19e5..32a3beaf4 100644 --- a/package.json +++ b/package.json @@ -966,14 +966,14 @@ }, { "command": "code-for-ibmi.debug.batch", - "title": "Start Debugging (Batch)", + "title": "Batch", "category": "IBM i", "icon": "$(debug-alt)", "enablement": "code-for-ibmi:connected" }, { "command": "code-for-ibmi.debug.sep", - "title": "Start Debugging (SEP)", + "title": "Service Entry Point", "category": "IBM i", "icon": "$(debug-alt)", "enablement": "code-for-ibmi:connected" @@ -1768,7 +1768,8 @@ }, { "id": "code-for-ibmi.debug.group", - "label": "Start Debugging" + "label": "Start Debugging", + "icon": "$(debug-start)" } ], "menus": { @@ -2465,7 +2466,7 @@ "group": "1_workspace@1" }, { - "command": "code-for-ibmi.debug.group", + "submenu": "code-for-ibmi.debug.group", "when": "view == objectBrowser && !inDebugMode && (viewItem =~ /^object.pgm.*/ || viewItem =~ /^object.srvpgm.*/)", "group": "2_debug@1" }, From 61197d810f216ae05ea60cb411f42153d8cbc639 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 11 Mar 2024 15:22:10 -0400 Subject: [PATCH 09/16] Correct function typo Signed-off-by: worksofliam --- src/api/debug/certificates.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/debug/certificates.ts b/src/api/debug/certificates.ts index 4126d2c46..c988c0031 100644 --- a/src/api/debug/certificates.ts +++ b/src/api/debug/certificates.ts @@ -24,7 +24,7 @@ function resolveHostnameToIP(hostName: string): Promise { }); } -async function getExtFileConent(host: string, connection: IBMi) { +async function getExtFileContent(host: string, connection: IBMi) { const ipRegexExp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gi; let hostname = undefined; let ipAddr = undefined; @@ -94,7 +94,7 @@ export async function remoteClientCertExists(connection: IBMi) { */ export async function setup(connection: IBMi) { const host = connection.currentHost; - const extFileContent = await getExtFileConent(host, connection); + const extFileContent = await getExtFileContent(host, connection); const commands = [ `openssl genrsa -out debug_service_ca.key 2048`, From 45eb7d73010603fe545b2b93ea40afd8a751b102 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 11 Mar 2024 15:38:16 -0400 Subject: [PATCH 10/16] Correct where package file is read from Signed-off-by: worksofliam --- src/api/debug/index.ts | 3 ++- src/api/debug/server.ts | 26 +++++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/api/debug/index.ts b/src/api/debug/index.ts index 6d82693a9..1f257f795 100644 --- a/src/api/debug/index.ts +++ b/src/api/debug/index.ts @@ -419,7 +419,7 @@ export async function initialize(context: ExtensionContext) { if (ptfInstalled) { vscode.window.withProgress({ location: vscode.ProgressLocation.Notification }, async (progress) => { progress.report({ message: `Ending Debug Service` }); - await server.stop(connection); + await server.stop(instance); }); } } @@ -428,6 +428,7 @@ export async function initialize(context: ExtensionContext) { // Run during startup: instance.onEvent("connected", async () => { + server.resetDebugServiceDetails() const connection = instance.getConnection(); const content = instance.getContent(); if (connection && content && debugPTFInstalled()) { diff --git a/src/api/debug/server.ts b/src/api/debug/server.ts index c97f9ec9c..493db91ed 100644 --- a/src/api/debug/server.ts +++ b/src/api/debug/server.ts @@ -6,8 +6,9 @@ import IBMiContent from "../IBMiContent"; import * as certificates from "./certificates"; import Instance from "../Instance"; -const directory = `/QIBM/ProdData/IBMiDebugService/bin/`; -const detailFile = `package,json`; +const directory = `/QIBM/ProdData/IBMiDebugService/`; +const binDirectory = path.posix.join(directory, `bin`); +const detailFile = `package.json`; const JavaPaths: {[version: string]: string} = { "8": `/QOpenSys/QIBM/ProdData/JavaVM/jdk80/64bit`, @@ -26,6 +27,10 @@ function getMyJavaHome(javaVersion: string) { } let debugServiceDetails: DebugServiceDetails | undefined; +export function resetDebugServiceDetails() { + debugServiceDetails = undefined; +} + export async function getDebugServiceDetails(content: IBMiContent): Promise { if (debugServiceDetails) { return debugServiceDetails; @@ -60,7 +65,7 @@ export async function startup(instance: Instance){ const javaHome = getMyJavaHome(details.java); const encryptResult = await connection.sendCommand({ - command: `${javaHome} MY_DBGSRV_SECURED_PORT="${config.debugPort}" MY_DBGSRV_SEP_DAEMON_PORT=${config.debugSepPort} DEBUG_SERVICE_KEYSTORE_PASSWORD="${host}" ${path.posix.join(directory, `encryptKeystorePassword.sh`)} | /usr/bin/tail -n 1` + command: `${javaHome} MY_DBGSRV_SECURED_PORT="${config.debugPort}" MY_DBGSRV_SEP_DAEMON_PORT=${config.debugSepPort} DEBUG_SERVICE_KEYSTORE_PASSWORD="${host}" ${path.posix.join(binDirectory, `encryptKeystorePassword.sh`)} | /usr/bin/tail -n 1` }); if ((encryptResult.code || 0) >= 1) { @@ -76,7 +81,7 @@ export async function startup(instance: Instance){ const keystorePath = certificates.getRemoteServerCertPath(connection); connection.sendCommand({ - command: `${javaHome} DEBUG_SERVICE_KEYSTORE_PASSWORD="${password}" DEBUG_SERVICE_KEYSTORE_FILE="${keystorePath}" /QOpenSys/usr/bin/nohup "${path.posix.join(directory, `startDebugService.sh`)}"` + command: `${javaHome} DEBUG_SERVICE_KEYSTORE_PASSWORD="${password}" DEBUG_SERVICE_KEYSTORE_FILE="${keystorePath}" /QOpenSys/usr/bin/nohup "${path.posix.join(binDirectory, `startDebugService.sh`)}"` }).then(startResult => { if ((startResult.code || 0) >= 1) { window.showErrorMessage(startResult.stdout || startResult.stderr); @@ -86,9 +91,16 @@ export async function startup(instance: Instance){ return; } -export async function stop(connection: IBMi) { +export async function stop(instance: Instance) { + const connection = instance.getConnection()!; + const content = instance.getContent()!; + const config = instance.getConfig()!; + + const details = await getDebugServiceDetails(content); + const javaHome = getMyJavaHome(details.java); + const endResult = await connection.sendCommand({ - command: `${path.posix.join(directory, `stopDebugService.sh`)}` + command: `${javaHome} ${path.posix.join(binDirectory, `stopDebugService.sh`)}` }); if (endResult.code === 0) { @@ -112,7 +124,7 @@ export async function end(instance: Instance): Promise { const javaHome = getMyJavaHome(details.java); const endResult = await connection.sendCommand({ - command: `${javaHome} ${path.posix.join(directory, `stopDebugService.sh`)}` + command: `${javaHome} ${path.posix.join(binDirectory, `stopDebugService.sh`)}` }); if (endResult.code && endResult.code >= 0) { From bdf011401663159c6ddd77197ed3949bb5ec76b0 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 26 Mar 2024 10:18:15 -0400 Subject: [PATCH 11/16] Change debugging labels Signed-off-by: worksofliam --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ff96058ec..3b349848c 100644 --- a/package.json +++ b/package.json @@ -962,14 +962,14 @@ }, { "command": "code-for-ibmi.debug.batch", - "title": "Batch", + "title": "Debug as Batch", "category": "IBM i", "icon": "$(debug-alt)", "enablement": "code-for-ibmi:connected" }, { "command": "code-for-ibmi.debug.sep", - "title": "Service Entry Point", + "title": "Set Service Entry Point", "category": "IBM i", "icon": "$(debug-alt)", "enablement": "code-for-ibmi:connected" From fadf90dfabd5d141fdd4c022e483e0920a8b7fe5 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 29 Mar 2024 10:46:50 -0400 Subject: [PATCH 12/16] Debug secure by default Signed-off-by: worksofliam --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 25d23c653..1b2b10d3f 100644 --- a/package.json +++ b/package.json @@ -382,7 +382,7 @@ }, "debugIsSecure": { "type": "boolean", - "default": false, + "default": true, "description": "Used to determine if the client should connect securely or not." }, "debugUpdateProductionFiles": { From bc0aa305e9844557cf9769cc26698eed5787bec9 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 1 Apr 2024 10:56:29 -0400 Subject: [PATCH 13/16] Correct configuration prop name Signed-off-by: worksofliam --- src/webviews/settings/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts index 047b1e0f4..d590b6ae7 100644 --- a/src/webviews/settings/index.ts +++ b/src/webviews/settings/index.ts @@ -175,7 +175,7 @@ export class SettingsUI { if (connection && connection.remoteFeatures[`startDebugService.sh`]) { debuggerTab .addInput(`debugPort`, `Debug port`, `Default secure port is 8005. Tells the client which port the debug service is running on.`, { default: config.debugPort, minlength: 1, maxlength: 5, regexTest: `^\\d+$` }) - .addInput(`debugPort`, `SEP debug port`, `Default secure port is 8008. Tells the client which port the debug service for SEP is running on.`, { default: config.debugSepPort, minlength: 1, maxlength: 5, regexTest: `^\\d+$` }) + .addInput(`debugSepPort`, `SEP debug port`, `Default secure port is 8008. Tells the client which port the debug service for SEP is running on.`, { default: config.debugSepPort, minlength: 1, maxlength: 5, regexTest: `^\\d+$` }) .addCheckbox(`debugUpdateProductionFiles`, `Update production files`, `Determines whether the job being debugged can update objects in production (*PROD) libraries.`, config.debugUpdateProductionFiles) .addCheckbox(`debugEnableDebugTracing`, `Debug trace`, `Tells the debug service to send more data to the client. Only useful for debugging issues in the service. Not recommended for general debugging.`, config.debugEnableDebugTracing); From 5a9d9d815986f409269f681bfcdad766e36113d2 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 2 Apr 2024 10:32:16 -0400 Subject: [PATCH 14/16] Refactor how client certificate gets created Signed-off-by: worksofliam --- src/api/debug/certificates.ts | 15 ++++++++++----- src/api/debug/index.ts | 15 +++------------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/api/debug/certificates.ts b/src/api/debug/certificates.ts index c78e5d450..198ae2391 100644 --- a/src/api/debug/certificates.ts +++ b/src/api/debug/certificates.ts @@ -65,6 +65,10 @@ function getLegacyCertificatePath() { return path.posix.join(LEGACY_CERT_DIRECTORY, SERVER_CERTIFICATE); } +function getPasswordForHost(connection: IBMi) { + return connection.currentHost; +} + export function getRemoteServerCertificatePath(connection: IBMi) { return path.posix.join(getRemoteCertificateDirectory(connection), SERVER_CERTIFICATE); } @@ -85,8 +89,8 @@ export async function remoteServerCertificateExists(connection: IBMi, legacy = f * Generate all certifcates on the server */ export async function setup(connection: IBMi) { - const host = connection.currentHost; - const extFileContent = await getExtFileContent(host, connection); + const pw = getPasswordForHost(connection); + const extFileContent = await getExtFileContent(pw, connection); if (!connection.usingBash()) { if (connection.remoteFeatures[`bash`]) { @@ -98,9 +102,9 @@ export async function setup(connection: IBMi) { const commands = [ `openssl genrsa -out debug_service.key 2048`, - `openssl req -new -key debug_service.key -out debug_service.csr -subj '/CN=${host}'`, + `openssl req -new -key debug_service.key -out debug_service.csr -subj '/CN=${pw}'`, `openssl x509 -req -in debug_service.csr -signkey debug_service.key -out debug_service.crt -days 1095 -sha256 -req -extfile <(printf "${extFileContent}")`, - `openssl pkcs12 -export -out debug_service.pfx -inkey debug_service.key -in debug_service.crt -password pass:${host}`, + `openssl pkcs12 -export -out debug_service.pfx -inkey debug_service.key -in debug_service.crt -password pass:${pw}`, `rm debug_service.key debug_service.csr debug_service.crt`, `chmod 444 debug_service.pfx` ]; @@ -127,9 +131,10 @@ export async function setup(connection: IBMi) { export async function downloadClientCert(connection: IBMi) { const localPath = getLocalCertPath(connection); + const keyPass = getPasswordForHost(connection); const result = await connection.sendCommand({ - command: `openssl s_client -connect localhost:${connection.config?.debugPort} -showcerts < /dev/null 2> /dev/null | openssl x509 -outform PEM`, + command: `openssl pkcs12 -in ${getRemoteServerCertificatePath(connection)} -passin pass:${keyPass} -info -nokeys -clcerts 2>/dev/null | openssl x509 -outform PEM`, directory: getRemoteCertificateDirectory(connection) }); diff --git a/src/api/debug/index.ts b/src/api/debug/index.ts index 9a7bf4e6e..ed2e49c04 100644 --- a/src/api/debug/index.ts +++ b/src/api/debug/index.ts @@ -318,22 +318,13 @@ export async function initialize(context: ExtensionContext) { if (connection.config!.debugIsSecure) { try { - const existingDebugService = await server.getRunningJob(connection.config?.debugPort || "8005", instance.getContent()!); const remoteCertExists = await certificates.remoteServerCertificateExists(connection); // If the client certificate exists on the server, download it if (remoteCertExists) { - if (existingDebugService) { - await certificates.downloadClientCert(connection); - localCertsOk = true; - vscode.window.showInformationMessage(`Debug client certificate downloaded from the server.`); - } else { - vscode.window.showInformationMessage(`Cannot fetch client certificate because the Debug Service is not running.`, `Startup Service`).then(result => { - if (result) { - vscode.commands.executeCommand(`code-for-ibmi.debug.start`); - } - }); - } + await certificates.downloadClientCert(connection); + localCertsOk = true; + vscode.window.showInformationMessage(`Debug client certificate downloaded from the server.`); } else { const doImport = await vscode.window.showInformationMessage(`Debug setup`, { modal: true, From 17d7837de48a65ddeb37ac8e0c0e91d89bd74c2b Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 2 Apr 2024 10:44:35 -0400 Subject: [PATCH 15/16] Ensure debug extension is activated when connecting Signed-off-by: worksofliam --- src/api/debug/index.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/api/debug/index.ts b/src/api/debug/index.ts index ed2e49c04..6161d4d43 100644 --- a/src/api/debug/index.ts +++ b/src/api/debug/index.ts @@ -11,6 +11,7 @@ import { ObjectItem } from "../../typings"; import { getEnvConfig } from "../local/env"; import * as certificates from "./certificates"; import * as server from "./server"; +import { debug } from "console"; const debugExtensionId = `IBM.ibmidebug`; @@ -26,11 +27,19 @@ export function isManaged() { return process.env[`DEBUG_MANAGED`] === `true`; } -export async function initialize(context: ExtensionContext) { - const debugExtensionAvailable = () => { - const debugclient = vscode.extensions.getExtension(debugExtensionId); - return debugclient !== undefined; +const activateDebugExtension = async () => { + const debugclient = vscode.extensions.getExtension(debugExtensionId); + if (debugclient && !debugclient.isActive) { + await debugclient.activate(); } +} + +const debugExtensionAvailable = () => { + const debugclient = vscode.extensions.getExtension(debugExtensionId); + return debugclient && debugclient.isActive; +} + +export async function initialize(context: ExtensionContext) { const startDebugging = async (type: DebugType, objectType: DebugObjectType, objectLibrary: string, objectName: string, workspaceFolder?: vscode.WorkspaceFolder) => { if (debugExtensionAvailable()) { @@ -437,6 +446,7 @@ export async function initialize(context: ExtensionContext) { // Run during startup: instance.onEvent("connected", async () => { + activateDebugExtension(); server.resetDebugServiceDetails() const connection = instance.getConnection(); const content = instance.getContent(); From 38a824323a31c3ad3b386010c8a07c1d024e1606 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 2 Apr 2024 14:02:09 -0400 Subject: [PATCH 16/16] Only ignore cert errors when not secure Signed-off-by: worksofliam --- src/api/debug/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/debug/index.ts b/src/api/debug/index.ts index 6161d4d43..a90774cd9 100644 --- a/src/api/debug/index.ts +++ b/src/api/debug/index.ts @@ -589,7 +589,7 @@ export async function startDebug(instance: Instance, options: DebugOptions) { "host": connection!.currentHost, "port": port, "secure": secure, // Enforce secure mode - "ignoreCertificateErrors": true, + "ignoreCertificateErrors": !secure, "subType": "batch", "library": options.library.toUpperCase(), "program": options.object.toUpperCase(),