From f23f79f79010131923e8a22557265e7ca93d3c13 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Wed, 28 Feb 2024 14:48:20 -0800 Subject: [PATCH] Assorted Cleanup (#9) * remove term "provenance database" * detect missing and expired cloud cred errors * order wf ids by created_at DESC * minor cleanup * move cloud cli logic to separate file * fix file name convention * ProvenanceDatabaseConfig * fix ssl setting in ProvenanceDatabase.connect * readme * marked extension as preview in package.json * don't include .github files in packed extension * add current dbos logo to extension * update changelog and version for early adopter release * update CHANGELOG --- .vscodeignore | 2 + CHANGELOG.md | 11 +- README.md | 43 ++++++- dbos-logo-128.png | Bin 0 -> 8681 bytes images/ttdbg-code-lens.png | Bin 0 -> 42746 bytes images/ttdbg-wfid-quick-pick.png | Bin 0 -> 38394 bytes package.json | 4 +- src/DebugProxy.ts | 19 ++-- src/ProvenanceDatabase.ts | 28 +++-- src/cloudCli.ts | 106 +++++++++++++++++ src/commands.ts | 41 ++++--- src/configuration.ts | 188 +++++++------------------------ src/utils.ts | 26 ++++- version.json | 2 +- 14 files changed, 279 insertions(+), 191 deletions(-) create mode 100644 dbos-logo-128.png create mode 100644 images/ttdbg-code-lens.png create mode 100644 images/ttdbg-wfid-quick-pick.png create mode 100644 src/cloudCli.ts diff --git a/.vscodeignore b/.vscodeignore index 72aa0fe..b305421 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,6 +1,7 @@ .vscode/** .vscode-test/** src/** +.github/** .gitignore .yarnrc vsc-extension-quickstart.md @@ -9,3 +10,4 @@ vsc-extension-quickstart.md **/*.map **/*.ts **/.vscode-test.* + diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c9ab6f..f53588c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,13 @@ All notable changes to the "dbos-ttdbg" extension will be documented in this file. -Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/). +This project adheres to [Semantic Versioning](https://semver.org) and uses +[NerdBank.GitVersioning](https://github.com/AArnott/Nerdbank.GitVersioning) to manage version numbers. -## [Unreleased] +As per [VSCode recommendation](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions), +this project uses *EVEN* minor version numbers for release versions and *ODD* minor version numbers for pre-release versions, -- Initial release \ No newline at end of file +## [0.9] + +- Initial preview release \ No newline at end of file diff --git a/README.md b/README.md index 3837fc8..f757af0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,45 @@ -# dbos-ttdbg README +# DBOS Time Travel Debugger + +This extension enables developers to debug their DBOS applications deployed to the DBOS Cloud using VS Code. +DBOS Cloud allows time travel debugging of any DBOS application execution that has occurred in the past three days. + +## Time Travel Debug CodeLens + +The DBOS Time Travel Debugger extension installed automatically attaches a "⏳ Time Travel Debug" +[CodeLens](https://code.visualstudio.com/blogs/2017/02/12/code-lens-roundup) +to every DBOS [workflow](https://docs.dbos.dev/tutorials/workflow-tutorial), +[transaction](https://docs.dbos.dev/tutorials/transaction-tutorial) +and [communicator](https://docs.dbos.dev/tutorials/communicator-tutorial) method in your DBOS application. + +![DBOS Time Travel CodeLens Screenshot](images/ttdbg-code-lens.png) + +When you click on the Time Travel Debug CodeLens, you are provided a list of recent executions of that method to debug. + +![DBOS Time Travel Workflow ID picker](images/ttdbg-wfid-quick-pick.png) + +After selecting a recent execution of your function, the DBOS Time Travel Debugger will launch the DBOS debug runtime +and VS Code typescript debugger. This allows you to debug your DBOS application against the database as it existed +at the time the selected execution originally occurred. + +For more information, please see the [official DBOS documentation](https://docs.dbos.dev/). + +## Installation + +The latest released version of the DBOS Time Travel Debugger for VS Code can be installed via the +[VS Code Marketplace](https://marketplace.visualstudio.com/publishers/dbos-inc). + +DBOS depends on [Node.js](https://nodejs.org/) version 20 or later. +The DBOS Time Travel Debugger for VS Code has no additional dependencies beyond what DBOS depends on. + +### Preview Releases + +The DBOS Time Travel Debugger builds every commit in our [GitHub repo](https://github.com/dbos-inc/ttdbg-extension). +You can install a preview build of the Time Travel Debugger extension navigating to a recent +[GitHub action run](https://github.com/dbos-inc/ttdbg-extension/actions/workflows/on_push.yml) +and downloading the associated "Extension" build artifact. +The "Extension" build artifact is a zip file containing the Time Travel Debugger's VSIX file, which can be installed manually. +For more information on installing VSIX extensions in Visual Studio Code, please see the +[official Visual Studio Code docs](https://code.visualstudio.com/docs/editor/extension-gallery#_install-from-a-vsix). ## Versioning Strategy diff --git a/dbos-logo-128.png b/dbos-logo-128.png new file mode 100644 index 0000000000000000000000000000000000000000..6639158fa3b8fe1a523ce55af3d687f00a9ab677 GIT binary patch literal 8681 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 literal 0 HcmV?d00001 diff --git a/images/ttdbg-code-lens.png b/images/ttdbg-code-lens.png new file mode 100644 index 0000000000000000000000000000000000000000..b476a5dab77a9401c708bab666c04ad0f434d441 GIT binary patch literal 42746 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 HW}*KFCtDFW literal 0 HcmV?d00001 diff --git a/images/ttdbg-wfid-quick-pick.png b/images/ttdbg-wfid-quick-pick.png new file mode 100644 index 0000000000000000000000000000000000000000..7e6d4961773e8879c0abc85f9318800fe4915df3 GIT binary patch literal 38394 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 3755a0f..b131b5f 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,11 @@ "displayName": "DBOS Time Travel Debugger", "publisher": "dbos-inc", "version": "0.0.0-placeholder", + "icon": "dbos-logo-128.png", "engines": { "vscode": "^1.86.0" }, + "preview": true, "categories": [ "Other" ], @@ -30,7 +32,7 @@ }, { "command": "dbos-ttdbg.delete-prov-db-passwords", - "title": "Delete Stored Provenance DB Passwords", + "title": "Delete Stored Application Database Passwords", "category": "DBOS" }, { diff --git a/src/DebugProxy.ts b/src/DebugProxy.ts index a79ddda..e16b815 100644 --- a/src/DebugProxy.ts +++ b/src/DebugProxy.ts @@ -6,7 +6,7 @@ import * as semver from 'semver'; import { CloudStorage } from './CloudStorage'; import { config, logger } from './extension'; import { execFile, exists, hashClientConfig } from './utils'; -import { ClientConfig } from 'pg'; +import { ProvenanceDatabaseConfig } from './configuration'; const IS_WINDOWS = process.platform === "win32"; const EXE_FILE_NAME = `debug-proxy${IS_WINDOWS ? ".exe" : ""}`; @@ -46,11 +46,11 @@ export class DebugProxy { } } - async launch(clientConfig: ClientConfig) { + async launch(clientConfig: ProvenanceDatabaseConfig): Promise { const configHash = hashClientConfig(clientConfig); if (!configHash) { throw new Error("Invalid configuration"); } - if (this._proxyProcesses.has(configHash)) { return; } + if (this._proxyProcesses.has(configHash)) { return true; } const exeUri = exeFileName(this.storageUri); const exeExists = await exists(exeUri); @@ -60,12 +60,15 @@ export class DebugProxy { const proxy_port = config.proxyPort; let { host, port, database, user, password } = clientConfig; - if (typeof password === "function") { - password = await password(); - if (!password) { - throw new Error("Provenance database password is required"); + if (typeof password === "function") { + const $password = await password(); + if ($password) { + password = $password; + } else { + return false; } } + if (!host || !database || !user || !password) { throw new Error("Invalid configuration"); } @@ -130,6 +133,8 @@ export class DebugProxy { this._proxyProcesses.delete(configHash); this._outChannel.info(`Debug Proxy exited with exit code ${code}`, { database }); }); + + return true; } async getVersion() { diff --git a/src/ProvenanceDatabase.ts b/src/ProvenanceDatabase.ts index 1ac1dfc..15eacfe 100644 --- a/src/ProvenanceDatabase.ts +++ b/src/ProvenanceDatabase.ts @@ -1,8 +1,8 @@ -import * as vscode from 'vscode'; import { Client, ClientConfig } from 'pg'; -import { config, logger } from './extension'; +import { logger } from './extension'; import { DbosMethodType, getDbosWorkflowName } from './sourceParser'; import { hashClientConfig } from './utils'; +import { ProvenanceDatabaseConfig } from './configuration'; export interface workflow_status { workflow_uuid: string; @@ -28,28 +28,38 @@ export class ProvenanceDatabase { } } - private async connect(clientConfig: ClientConfig): Promise { - const configHash = hashClientConfig(clientConfig); + private async connect(dbConfig: ProvenanceDatabaseConfig): Promise { + const configHash = hashClientConfig(dbConfig); if (!configHash) { throw new Error("Invalid configuration"); } const existingDB = this._databases.get(configHash); if (existingDB) { return existingDB; } - const db = new Client(clientConfig); + const password = typeof dbConfig.password === "function" ? await dbConfig.password() : dbConfig.password; + if (!password) { throw new Error("Invalid password"); } + + const db = new Client({ + host: dbConfig.host, + port: dbConfig.port, + database: dbConfig.database, + user: dbConfig.user, + password, + ssl: { rejectUnauthorized: false } + }); await db.connect(); this._databases.set(configHash, db); return db; } - async getWorkflowStatuses(clientConfig: ClientConfig, name: string, $type: DbosMethodType): Promise { + async getWorkflowStatuses(clientConfig: ProvenanceDatabaseConfig, name: string, $type: DbosMethodType): Promise { const wfName = getDbosWorkflowName(name, $type); const db = await this.connect(clientConfig); - const results = await db.query('SELECT * FROM dbos.workflow_status WHERE name = $1 LIMIT 10', [wfName]); + const results = await db.query('SELECT * FROM dbos.workflow_status WHERE name = $1 ORDER BY created_at DESC LIMIT 10', [wfName]); return results.rows; } - async getWorkflowStatus(clientConfig: ClientConfig, wfid: string): Promise { + async getWorkflowStatus(clientConfig: ProvenanceDatabaseConfig, wfid: string): Promise { const db = await this.connect(clientConfig); - const results = await db.query('SELECT * FROM dbos.workflow_status WHERE workflow_uuid = $1 LIMIT 10', [wfid]); + const results = await db.query('SELECT * FROM dbos.workflow_status WHERE workflow_uuid = $1', [wfid]); if (results.rows.length > 1) { throw new Error(`Multiple workflow status records found for workflow ID ${wfid}`); } return results.rows.length === 1 ? results.rows[0] : undefined; } diff --git a/src/cloudCli.ts b/src/cloudCli.ts new file mode 100644 index 0000000..879e127 --- /dev/null +++ b/src/cloudCli.ts @@ -0,0 +1,106 @@ +import * as vscode from 'vscode'; +import { spawn as cpSpawn } from "child_process"; +import { execFile } from './utils'; +import { logger } from './extension'; + +export interface DbosCloudApp { + Name: string; + ID: string; + PostgresInstanceName: string; + ApplicationDatabaseName: string; + Status: string; + Version: string; +} + +export interface DbosCloudDatabase { + PostgresInstanceName: string; + HostName: string; + Status: string; + Port: number; + AdminUsername: string; +} + +async function dbos_cloud_cli(folder: vscode.WorkspaceFolder, ...args: string[]): Promise { + const { stdout } = await execFile("npx", ["dbos-cloud", ...args, "--json"], { + cwd: folder.uri.fsPath, + }); + return JSON.parse(stdout) as T; +} + +export async function dbos_cloud_app_status(folder: vscode.WorkspaceFolder) { + return dbos_cloud_cli(folder, "application", "status"); +} + +export async function dbos_cloud_db_status(folder: vscode.WorkspaceFolder, databaseName: string) { + return dbos_cloud_cli(folder, "database", "status", databaseName); +} + +export async function dbos_cloud_login(folder: vscode.WorkspaceFolder) { + logger.debug("dbos_cloud_login", { folder: folder.uri.fsPath }); + + const cts = new vscode.CancellationTokenSource(); + const loginProc = cpSpawn("npx", ["dbos-cloud", "login"], { cwd: folder.uri.fsPath }); + const userCodeEmitter = new vscode.EventEmitter(); + + const regexLoginInfo = /Login URL: (http.*\/activate\?user_code=([A-Z][A-Z][A-Z][A-Z]-[A-Z][A-Z][A-Z][A-Z]))/; + const regexSuccessfulLogin = /Successfully logged in as (.*)!/; + + try { + const ctsPromise = new Promise(resolve => { + cts.token.onCancellationRequested(() => resolve()); + }); + + loginProc.on('exit', (code) => { logger.debug("dbos_cloud_login on proc exit", { code }); cts.cancel(); }); + loginProc.on('close', (code) => { logger.debug("dbos_cloud_login on proc close", { code }); cts.cancel(); }); + loginProc.on('error', err => { logger.error("dbos_cloud_login on proc error", err); cts.cancel(); }); + + loginProc.stdout.on("data", async (buffer: Buffer) => { + const data = buffer.toString().trim(); + logger.debug("dbos_cloud_login on stdout data", { data }); + + const loginUrlMatch = regexLoginInfo.exec(data); + if (loginUrlMatch && loginUrlMatch.length === 3) { + const [, loginUrl, userCode] = loginUrlMatch; + logger.info("dbos_cloud_login login info", { loginUri: loginUrl, userCode }); + userCodeEmitter.fire(userCode); + + const openResult = await vscode.env.openExternal(vscode.Uri.parse(loginUrl)); + if (!openResult) { + logger.error("dbos_cloud_login openExternal failed", { loginUri: loginUrl, userCode }); + cts.cancel(); + } + } + + const successfulLoginMatch = regexSuccessfulLogin.exec(data); + if (successfulLoginMatch && successfulLoginMatch.length === 2) { + const [, user] = successfulLoginMatch; + logger.info("dbos-dbos_cloud_login successful login", { user }); + vscode.window.showInformationMessage(`Successfully logged in to DBOS Cloud as ${user}`); + } + }); + + await vscode.window.withProgress({ + cancellable: true, + location: vscode.ProgressLocation.Notification, + title: "Launching browser to log into DBOS Cloud" + }, async (progress, token) => { + userCodeEmitter.event(userCode => { + progress.report({ message: `\nUser code: ${userCode}` }); + }); + + token.onCancellationRequested(() => cts.cancel()); + await ctsPromise; + }); + } finally { + loginProc.stdout.removeAllListeners(); + loginProc.stderr.removeAllListeners(); + loginProc.removeAllListeners(); + + cts.dispose(); + userCodeEmitter.dispose(); + + const killed = loginProc.killed; + const killResult = loginProc.kill(); + logger.debug("dbos_cloud_login exit", { killed, killResult }); + } +} diff --git a/src/commands.ts b/src/commands.ts index 271b314..c86ba65 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -2,8 +2,8 @@ import * as vscode from 'vscode'; import { logger, config, provDB, debugProxy } from './extension'; import { DbosMethodType } from "./sourceParser"; import { getWorkspaceFolder, isQuickPickItem, showQuickPick } from './utils'; -import { dbos_cloud_login } from './configuration'; -import { ClientConfig } from 'pg'; +import { dbos_cloud_login } from './cloudCli'; +import { ProvenanceDatabaseConfig } from './configuration'; export const cloudLoginCommandName = "dbos-ttdbg.cloud-login"; export const startDebuggingCodeLensCommandName = "dbos-ttdbg.start-debugging-code-lens"; @@ -11,29 +11,40 @@ export const startDebuggingUriCommandName = "dbos-ttdbg.start-debugging-uri"; export const shutdownDebugProxyCommandName = "dbos-ttdbg.shutdown-debug-proxy"; export const deleteProvDbPasswordsCommandName = "dbos-ttdbg.delete-prov-db-passwords"; -async function startDebugging(folder: vscode.WorkspaceFolder, getWorkflowID: (clientConfig: ClientConfig) => Promise) { +async function startDebugging(folder: vscode.WorkspaceFolder, getWorkflowID: (clientConfig: ProvenanceDatabaseConfig) => Promise) { try { - const clientConfig = await config.getProvDbConfig(folder); - if (!clientConfig) { return; } - await vscode.window.withProgress( { location: vscode.ProgressLocation.Window, title: "Launching DBOS Time Travel Debugger", }, async () => { - await debugProxy.launch(clientConfig); - const workflowID = await getWorkflowID(clientConfig); - if (!workflowID) { return; } + const dbConfig = await config.getProvDBConfig(folder); + if (!dbConfig) { + logger.warn("startDebugging: config.getProvDBConfig returned undefined"); + return; + } + + const proxyLaunched = await debugProxy.launch(dbConfig); + if (!proxyLaunched) { + logger.warn("startDebugging: debugProxy.launch returned false"); + return; + } + + const workflowID = await getWorkflowID(dbConfig); + if (!workflowID) { + logger.warn("startDebugging: getWorkflowID returned undefined"); + return; + } - const workflowStatus = await provDB.getWorkflowStatus(clientConfig, workflowID); + const workflowStatus = await provDB.getWorkflowStatus(dbConfig, workflowID); if (!workflowStatus) { - vscode.window.showErrorMessage(`Workflow ID ${workflowID} not found in provenance database`); + vscode.window.showErrorMessage(`Workflow ID ${workflowID} not found`); return; } const proxyURL = `http://localhost:${config.proxyPort ?? 2345}`; - logger.info(`startDebugging`, { folder: folder.uri.fsPath, database: clientConfig.database, workflowID }); + logger.info(`startDebugging`, { folder: folder.uri.fsPath, database: dbConfig.database, workflowID }); const debuggerStarted = await vscode.debug.startDebugging( folder, { @@ -49,8 +60,6 @@ async function startDebugging(folder: vscode.WorkspaceFolder, getWorkflowID: (cl } } ); - - } catch (e) { logger.error("startDebugging", e); vscode.window.showErrorMessage(`Failed to start debugging`); @@ -59,8 +68,8 @@ async function startDebugging(folder: vscode.WorkspaceFolder, getWorkflowID: (cl export async function startDebuggingFromCodeLens(folder: vscode.WorkspaceFolder, name: string, $type: DbosMethodType) { logger.info(`startDebuggingFromCodeLens`, { folder: folder.uri.fsPath, name, type: $type }); - await startDebugging(folder, async (clientConfig) => { - const statuses = await provDB.getWorkflowStatuses(clientConfig, name, $type); + await startDebugging(folder, async (dbConfig) => { + const statuses = await provDB.getWorkflowStatuses(dbConfig, name, $type); const items = statuses.map(s => { label: new Date(parseInt(s.created_at)).toLocaleString(), description: `${s.authenticated_user.length === 0 ? "" : s.authenticated_user} (${s.status})`, diff --git a/src/configuration.ts b/src/configuration.ts index 1e54ca7..caf95e4 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1,8 +1,8 @@ import * as vscode from 'vscode'; -import { SpawnOptions, spawn as cpSpawn } from "child_process"; import { ClientConfig } from 'pg'; -import { execFile, exists } from './utils'; +import { exists, isExecFileError } from './utils'; import { logger } from './extension'; +import { dbos_cloud_app_status, dbos_cloud_db_status, dbos_cloud_login } from './cloudCli'; const TTDBG_CONFIG_SECTION = "dbos-ttdbg"; const PROV_DB_HOST = "prov_db_host"; @@ -11,133 +11,7 @@ const PROV_DB_DATABASE = "prov_db_database"; const PROV_DB_USER = "prov_db_user"; const DEBUG_PROXY_PORT = "debug_proxy_port"; -export interface DbosCloudApp { - Name: string; - ID: string; - PostgresInstanceName: string; - ApplicationDatabaseName: string; - Status: string; - Version: string; -} - -export interface DbosCloudDatabase { - PostgresInstanceName: string; - HostName: string; - Status: string; - Port: number; - AdminUsername: string; -} - -async function dbos_cloud_cli(folder: vscode.WorkspaceFolder, ...args: string[]): Promise { - const { stdout } = await execFile("npx", ["dbos-cloud", ...args, "--json"], { - cwd: folder.uri.fsPath, - }); - return JSON.parse(stdout) as T; -} - -async function dbos_cloud_app_status(folder: vscode.WorkspaceFolder) { - return dbos_cloud_cli(folder, "application", "status"); -} - -async function dbos_cloud_db_status(folder: vscode.WorkspaceFolder, databaseName: string) { - return dbos_cloud_cli(folder, "database", "status", databaseName); -} - -export async function dbos_cloud_login(folder: vscode.WorkspaceFolder) { - logger.info("dbos_cloud_login", { folder: folder.uri.fsPath }); - - const cts = new vscode.CancellationTokenSource(); - const loginProc = cpSpawn("npx", ["dbos-cloud", "login"], { cwd: folder.uri.fsPath }); - const userCodeEmitter = new vscode.EventEmitter(); - - const regexLoginUrl = /Login URL: (http.*\/activate\?user_code=([A-Z][A-Z][A-Z][A-Z]-[A-Z][A-Z][A-Z][A-Z]))/; - const regexSuccessfulLogin = /Successfully logged in as (.*)!/; - - try { - const ctsPromise = new Promise(resolve => { - cts.token.onCancellationRequested(() => resolve()); - }); - - loginProc.on('exit', () => { logger.info("dbos-cloud login on exit"); cts.cancel(); }); - loginProc.on('close', () => { logger.info("dbos-cloud login on close"); cts.cancel(); }); - loginProc.on('error', err => { logger.error("dbos-cloud login on error", err); cts.cancel(); }); - - loginProc.stdout.on("data", async (buffer: Buffer) => { - const data = buffer.toString().trim(); - logger.info("dbos-cloud login stdout on data", { data }); - - const loginUrlMatch = regexLoginUrl.exec(data); - if (loginUrlMatch && loginUrlMatch.length === 3) { - const [, loginUrl, userCode] = loginUrlMatch; - logger.info("dbos-cloud login url", { loginUri: loginUrl, userCode }); - userCodeEmitter.fire(userCode); - - const openResult = await vscode.env.openExternal(vscode.Uri.parse(loginUrl)); - if (!openResult) { - logger.error("dbos_cloud_login openExternal failed", { loginUri: loginUrl, userCode }); - cts.cancel(); - } - } - - const successfulLoginMatch = regexSuccessfulLogin.exec(data); - if (successfulLoginMatch && successfulLoginMatch.length === 2) { - const [, user] = successfulLoginMatch; - logger.info("dbos-cloud login successful", { user }); - vscode.window.showInformationMessage(`Successfully logged in to DBOS Cloud as ${user}`); - } - }); - - await vscode.window.withProgress({ - cancellable: true, - location: vscode.ProgressLocation.Notification, - title: "Launching browser to log into DBOS Cloud" - }, async (progress, token) => { - userCodeEmitter.event(userCode => { - progress.report({ message: `\nUser code: ${userCode}` }); - }); - - token.onCancellationRequested(() => cts.cancel()); - await ctsPromise; - }); - } finally { - loginProc.stdout.removeAllListeners(); - loginProc.stderr.removeAllListeners(); - loginProc.removeAllListeners(); - - cts.dispose(); - userCodeEmitter.dispose(); - - const killed = loginProc.killed; - const killResult = loginProc.kill(); - logger.info("dbos_cloud_login exit", { killed, killResult }); - } -} - -interface ExecFileError { - cmd: string; - code: number; - killed: boolean; - stdout: string; - stderr: string; - message: string; - stack: string; -} - -function isExecFileError(e: unknown): e is ExecFileError { - if (e instanceof Error) { - return "stdout" in e && "stderr" in e && "cmd" in e; - } - return false; -} - -interface DatabaseConfig { - host: string | undefined; - port: number | undefined; - database: string | undefined; - user: string | undefined; -} - -async function getDbConfigFromDbosCloud(folder: vscode.WorkspaceFolder): Promise { +async function getProvDBConfigFromDbosCloud(folder: vscode.WorkspaceFolder): Promise { try { const app = await dbos_cloud_app_status(folder); const db = await dbos_cloud_db_status(folder, app.PostgresInstanceName); @@ -148,15 +22,16 @@ async function getDbConfigFromDbosCloud(folder: vscode.WorkspaceFolder): Promise user: db.AdminUsername }; } catch (e) { - if (isExecFileError(e) && e.stdout.includes("Error: not logged in")) { - return undefined; - } else { - throw e; + if (isExecFileError(e)) { + if (e.stdout.includes("Error: not logged in") || e.stdout.includes("Error: Login expired")) { + return undefined; + } } + throw e; } } -function getDbConfigFromVSCodeConfig(folder: vscode.WorkspaceFolder): DatabaseConfig { +function getProvDBConfigFromVSCodeConfig(folder: vscode.WorkspaceFolder): ProvenanceDatabaseConfig { const cfg = vscode.workspace.getConfiguration(TTDBG_CONFIG_SECTION, folder); const host = cfg.get(PROV_DB_HOST); @@ -176,25 +51,41 @@ async function startInvalidCredentialsFlow(folder: vscode.WorkspaceFolder): Prom const credentialsPath = vscode.Uri.joinPath(folder.uri, ".dbos", "credentials"); const credentialsExists = await exists(credentialsPath); - // TODO: Register support - const result = await vscode.window.showWarningMessage( - "Invalid DBOS Cloud credentials", - "Login", "Cancel"); + const message = credentialsExists + ? "DBOS Cloud credentials have expired. Please login again." + : "You need to login to DBOS Cloud."; - if (result === "Login") { - await dbos_cloud_login(folder); + const items = ["Login", "Cancel"]; + + // TODO: Register support + // if (!credentialsExists) { items.unshift("Register"); } + + const result = await vscode.window.showWarningMessage(message, ...items); + switch (result) { + // case "Register": break; + case "Login": + await dbos_cloud_login(folder); + break; } } +export interface ProvenanceDatabaseConfig { + user?: string | undefined; + database?: string | undefined; + password?: string | undefined | (() => Promise); + port?: number | undefined; + host?: string | undefined; +} + export class Configuration { constructor(private readonly secrets: vscode.SecretStorage) { } - async getProvDbConfig(folder: vscode.WorkspaceFolder): Promise { + async getProvDBConfig(folder: vscode.WorkspaceFolder): Promise { const dbConfig = await vscode.window.withProgress( { location: vscode.ProgressLocation.Window }, async () => { - const cloudConfig = await getDbConfigFromDbosCloud(folder); - const localConfig = getDbConfigFromVSCodeConfig(folder); + const cloudConfig = await getProvDBConfigFromDbosCloud(folder); + const localConfig = getProvDBConfigFromVSCodeConfig(folder); const host = localConfig?.host ?? cloudConfig?.host; const port = localConfig?.port ?? cloudConfig?.port ?? 5432; @@ -211,7 +102,6 @@ export class Configuration { database: dbConfig.database, user: dbConfig.user, password: () => this.#getPassword(folder), - ssl: { rejectUnauthorized: false } }; } else { startInvalidCredentialsFlow(folder).catch(e => logger.error("startInvalidCredentialsFlow", e)); @@ -228,18 +118,17 @@ export class Configuration { return `${TTDBG_CONFIG_SECTION}.prov_db_password.${folder.uri.fsPath}`; } - async #getPassword(folder: vscode.WorkspaceFolder): Promise { + async #getPassword(folder: vscode.WorkspaceFolder): Promise { const passwordKey = this.#getPasswordKey(folder); let password = await this.secrets.get(passwordKey); if (!password) { password = await vscode.window.showInputBox({ - prompt: "Enter provenance database password", + prompt: "Enter application database password", password: true, }); - if (!password) { - throw new Error('Provenance database password is required'); + if (password) { + await this.secrets.store(passwordKey, password); } - await this.secrets.store(passwordKey, password); } return password; } @@ -251,3 +140,4 @@ export class Configuration { } } } + diff --git a/src/utils.ts b/src/utils.ts index 6ff5065..b1bf483 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,6 +3,7 @@ import { execFile as cpExecFile } from "child_process"; import util from 'util'; import { fast1a32 } from 'fnv-plus'; import { ClientConfig } from 'pg'; +import { ProvenanceDatabaseConfig } from './configuration'; export const PLATFORM = function () { switch (process.platform) { @@ -42,7 +43,7 @@ export async function exists(uri: vscode.Uri): Promise { export const execFile = util.promisify(cpExecFile); -export function hashClientConfig(clientConfig: ClientConfig) { +export function hashClientConfig(clientConfig: ClientConfig | ProvenanceDatabaseConfig) { const { host, port, database, user } = clientConfig; return host && port && database && user ? fast1a32(`${host}:${port}:${database}:${user}`) @@ -70,16 +71,16 @@ export interface QuickPickOptions { placeHolder?: string; } -export type QuickPickResult = vscode.QuickPickItem | vscode.QuickInputButton; +export type QuickPickResult = vscode.QuickPickItem | vscode.QuickInputButton | undefined; -export function isQuickPickItem(item?: QuickPickResult): item is vscode.QuickPickItem { +export function isQuickPickItem(item: QuickPickResult): item is vscode.QuickPickItem { return item !== undefined && "label" in item; } export async function showQuickPick(options: QuickPickOptions) { const disposables: { dispose(): any }[] = []; try { - return await new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { const input = vscode.window.createQuickPick(); input.title = options.title; input.placeholder = options.placeHolder; @@ -111,3 +112,20 @@ export async function showQuickPick(options: QuickPickOptions) { disposables.forEach(d => d.dispose()); } } + +export interface ExecFileError { + cmd: string; + code: number; + killed: boolean; + stdout: string; + stderr: string; + message: string; + stack: string; +} + +export function isExecFileError(e: unknown): e is ExecFileError { + if (e instanceof Error) { + return "stdout" in e && "stderr" in e && "cmd" in e; + } + return false; +} \ No newline at end of file diff --git a/version.json b/version.json index 15fef15..2464712 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.6-preview", + "version": "0.9-preview", "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/heads/dev$",