Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retrieve prov DB info from cloud via dbos-cloud CLI #4

Merged
merged 11 commits into from
Feb 21, 2024
13 changes: 3 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,14 @@
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "dbos-ttdbg.launch-debug-proxy",
qianl15 marked this conversation as resolved.
Show resolved Hide resolved
"title": "Launch Debug Proxy",
"category": "DBOS"
},
{
"command": "dbos-ttdbg.shutdown-debug-proxy",
"title": "Shutdown Debug Proxy",
"category": "DBOS"
},

{
"command": "dbos-ttdbg.delete-prov-db-password",
"title": "Delete Stored Provenance DB Password",
"command": "dbos-ttdbg.delete-prov-db-passwords",
"title": "Delete Stored Provenance DB Passwords",
"category": "DBOS"
}
],
Expand All @@ -51,8 +45,7 @@
"type": "string"
},
"dbos-ttdbg.prov_db_port": {
"type": "number",
"default": 5432
"type": "number"
},
"dbos-ttdbg.prov_db_database": {
"type": "string"
Expand Down
62 changes: 27 additions & 35 deletions src/DebugProxy.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as vscode from 'vscode';
import { ChildProcessWithoutNullStreams as ChildProcess, spawn, execFile } from "child_process";
import { ChildProcessWithoutNullStreams as ChildProcess, spawn } from "child_process";
import jszip from 'jszip';
import * as fs from 'node:fs/promises';
import * as semver from 'semver';
import { CloudStorage } from './CloudStorage';
import { config, logger } from './extension';
import { exists } from './utils';
import { execFile, exists } from './utils';

const IS_WINDOWS = process.platform === "win32";
const EXE_FILE_NAME = `debug-proxy${IS_WINDOWS ? ".exe" : ""}`;
Expand All @@ -24,7 +24,7 @@ function throwOnCancelled(token?: vscode.CancellationToken) {

export class DebugProxy {
private _outChannel: vscode.LogOutputChannel;
private _proxyProcess: ChildProcess | undefined;
private _proxyProcesses: Map<string, ChildProcess> = new Map();

constructor(private readonly cloudStorage: CloudStorage, private readonly storageUri: vscode.Uri) {
this._outChannel = vscode.window.createOutputChannel("DBOS Debug Proxy", { log: true });
Expand All @@ -35,19 +35,18 @@ export class DebugProxy {
}

shutdown() {
if (this._proxyProcess) {
const process = this._proxyProcess;
this._proxyProcess = undefined;
logger.info(`Debug Proxy shutting down`, { pid: process.pid });
for (const [key, process] of this._proxyProcesses.entries()) {
this._proxyProcesses.delete(key);
logger.info(`Debug Proxy shutting down`, { folder: key, pid: process.pid });
process.stdout.removeAllListeners();
process.stderr.removeAllListeners();
process.removeAllListeners();
process.kill();
}
}

async launch() {
if (this._proxyProcess) { return; }
async launch(folder: vscode.WorkspaceFolder) {
if (this._proxyProcesses.has(folder.uri.fsPath)) { return; }

const exeUri = exeFileName(this.storageUri);
const exeExists = await exists(exeUri);
Expand All @@ -56,9 +55,12 @@ export class DebugProxy {
}

const proxy_port = config.proxyPort;
let { host, port, database, user, password } = config.provDbConfig;
let { host, port, database, user, password } = await config.getProvDbConfig(folder);
if (typeof password === "function") {
password = await password();
if (!password) {
throw new Error("Provenance database password is required");
}
}
if (!host || !database || !user || !password) {
throw new Error("Invalid configuration");
Expand All @@ -77,7 +79,7 @@ export class DebugProxy {
args.push("-listen", `${proxy_port}`);
}

this._proxyProcess = spawn(
const proxyProcess = spawn(
exeUri.fsPath,
args,
{
Expand All @@ -86,35 +88,36 @@ export class DebugProxy {
}
}
);
logger.info(`Debug Proxy launched`, { port: proxy_port, pid: this._proxyProcess.pid });
logger.info(`Debug Proxy launched`, { port: proxy_port, pid: proxyProcess.pid, folder: folder.name });

this._proxyProcess.stdout.on("data", (data: Buffer) => {
proxyProcess.stdout.on("data", (data: Buffer) => {
const { time, level, msg, ...properties } = JSON.parse(data.toString()) as { time: string, level: string, msg: string, [key: string]: unknown };
const $properties = { ...properties, folder: folder.name };
switch (level.toLowerCase()) {
case "debug":
this._outChannel.debug(msg, properties);
this._outChannel.debug(msg, $properties);
break;
case "info":
this._outChannel.info(msg, properties);
this._outChannel.info(msg, $properties);
break;
case "warn":
this._outChannel.warn(msg, properties);
this._outChannel.warn(msg, $properties);
break;
case "error":
this._outChannel.error(msg, properties);
this._outChannel.error(msg, $properties);
break;
default:
this._outChannel.appendLine(`${time} [${level}] ${msg} ${JSON.stringify(properties)}`);
this._outChannel.appendLine(`${time} [${level}] ${msg} ${JSON.stringify($properties)}`);
break;
}
});

this._proxyProcess.on("error", e => {
this._outChannel.error(e);
proxyProcess.on("error", e => {
this._outChannel.error(e, { folder: folder.name });
});

this._proxyProcess.on("exit", (code, _signal) => {
this._outChannel.info(`Debug Proxy exited with exit code ${code}`);
proxyProcess.on("exit", (code, _signal) => {
this._outChannel.info(`Debug Proxy exited with exit code ${code}`, { folder: folder.name });
});
}

Expand Down Expand Up @@ -163,19 +166,8 @@ export class DebugProxy {
}

try {
return await new Promise<string | undefined>((resolve, reject) => {
execFile(exeUri.fsPath, ["-version"], (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
if (stderr) {
reject(stderr);
} else {
resolve(stdout.trim());
}
}
});
});
const { stdout } = await execFile(exeUri.fsPath, ["-version"]);
return stdout.trim();
} catch (e) {
logger.error("Failed to get local debug proxy version", e);
return undefined;
Expand Down
21 changes: 13 additions & 8 deletions src/ProvenanceDatabase.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as vscode from 'vscode';
import { Client } from 'pg';
import { config, logger } from './extension';
import { DbosMethodType, getDbosWorkflowName } from './sourceParser';
Expand All @@ -16,24 +17,28 @@ export interface workflow_status {
}

export class ProvenanceDatabase {
private _db: Client | undefined;
private _databases: Map<string, Client> = new Map();

dispose() {
this._db?.end(e => logger.error(e));
for (const db of this._databases.values()) {
db.end(e => logger.error(e));
}
}

async connect(): Promise<Client> {
if (this._db) { return this._db; }
private async connect(folder: vscode.WorkspaceFolder): Promise<Client> {
const existingDB = this._databases.get(folder.uri.fsPath);
if (existingDB) { return existingDB; }

const db = new Client(config.provDbConfig);
const provDbConfig = await config.getProvDbConfig(folder);
const db = new Client(provDbConfig);
await db.connect();
this._db = db;
this._databases.set(folder.uri.fsPath, db);
return db;
}

async getWorkflowStatuses(name: string, $type: DbosMethodType): Promise<workflow_status[]> {
async getWorkflowStatuses(folder: vscode.WorkspaceFolder, name: string, $type: DbosMethodType): Promise<workflow_status[]> {
const wfName = getDbosWorkflowName(name, $type);
const db = await this.connect();
const db = await this.connect(folder);
const results = await db.query<workflow_status>('SELECT * FROM dbos.workflow_status WHERE name = $1 LIMIT 10', [wfName]);
return results.rows;
}
Expand Down
5 changes: 4 additions & 1 deletion src/codeLensProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { getDbosMethodType, parse } from './sourceParser';
export class TTDbgCodeLensProvider implements vscode.CodeLensProvider {
provideCodeLenses(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.CodeLens[]> {
try {
const folder = vscode.workspace.getWorkspaceFolder(document.uri);
if (!folder) { return; }

const text = document.getText();
const file = ts.createSourceFile(
document.fileName,
Expand All @@ -30,7 +33,7 @@ export class TTDbgCodeLensProvider implements vscode.CodeLensProvider {
title: '⏳ Time Travel Debug',
tooltip: `Debug ${methodInfo.name} with the DBOS Time Travel Debugger`,
command: startDebuggingCommandName,
arguments: [methodInfo.name, methodType]
arguments: [folder, methodInfo.name, methodType]
});
})
.filter(<T>(x?: T): x is T => !!x);
Expand Down
47 changes: 29 additions & 18 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import * as vscode from 'vscode';
import { logger, config, provDB, debugProxy } from './extension';
import { DbosMethodType } from "./sourceParser";
import { stringify } from './utils';

export const startDebuggingCommandName = "dbos-ttdbg.startDebugging";
export const launchDebugProxyCommandName = "dbos-ttdbg.launch-debug-proxy";
export const shutdownDebugProxyCommandName = "dbos-ttdbg.shutdown-debug-proxy";
export const deleteProvenanceDatabasePasswordCommandName = "dbos-ttdbg.delete-prov-db-password";
export const deleteProvenanceDatabasePasswordCommandName = "dbos-ttdbg.delete-prov-db-passwords";

export async function startDebugging(name: string, $type: DbosMethodType) {
export async function startDebugging(folder: vscode.WorkspaceFolder, name: string, $type: DbosMethodType) {
try {
await debugProxy.launch();
const statuses = await provDB.getWorkflowStatuses(name, $type);
await debugProxy.launch(folder);
const statuses = await provDB.getWorkflowStatuses(folder, name, $type);

// TODO: eventually, we'll need a better UI than "list all workflow IDs and let the user pick one"
const wfID = await vscode.window.showQuickPick(statuses.map(s => s.workflow_uuid), {
Expand All @@ -33,20 +33,31 @@ export async function startDebugging(name: string, $type: DbosMethodType) {
}
);
} catch (e) {
const reason = stringify(e);
logger.error("startDebugging", e);
vscode.window.showErrorMessage("Failed to start debugging");
vscode.window.showErrorMessage(`Failed to start debugging\n${reason}`);
}
}

export async function launchDebugProxy() {
try {
await debugProxy.launch();
vscode.window.showInformationMessage("Debug proxy launched");
} catch (e) {
logger.error("launchDebugProxy", e);
vscode.window.showErrorMessage("Failed to launch debug proxy");
}
}
// export async function launchDebugProxy() {
// try {
// const folder = await getWorkspaceFolder();
// if (folder) {
// await debugProxy.launch(folder);
// vscode.window.showInformationMessage(`Debug proxy launched for ${folder.name}`);
// }
// } catch (e) {
// logger.error("launchDebugProxy", e);
// vscode.window.showErrorMessage("Failed to launch debug proxy");
// }

// async function getWorkspaceFolder() {
// const folders = vscode.workspace.workspaceFolders ?? [];
// if (folders.length === 0) { throw new Error("No workspace folders found"); }
// if (folders.length === 1) { return folders[0]; }
// return await vscode.window.showWorkspaceFolderPick();
// }
// }

export async function shutdownDebugProxy() {
try {
Expand All @@ -56,10 +67,10 @@ export async function shutdownDebugProxy() {
}
}

export async function deleteProvenanceDatabasePassword() {
export async function deleteProvenanceDatabasePasswords() {
try {
await config.deletePassword();
await config.deletePasswords();
} catch (e) {
logger.error("deleteProvenanceDatabasePassword", e);
logger.error("deleteProvenanceDatabasePasswords", e);
}
}
Loading