diff --git a/src/error.ts b/src/error.ts index 0e9babd9..31723fd3 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,4 +1,5 @@ import { isAxiosError } from "axios" +import { isApiError, isApiErrorResponse } from "coder/site/src/api/errors" import * as forge from "node-forge" import * as tls from "tls" import * as vscode from "vscode" @@ -157,3 +158,14 @@ export class CertificateError extends Error { } } } + +// getErrorDetail is copied from coder/site, but changes the default return. +export const getErrorDetail = (error: unknown): string | undefined | null => { + if (isApiError(error)) { + return error.response.data.detail + } + if (isApiErrorResponse(error)) { + return error.detail + } + return null +} diff --git a/src/extension.ts b/src/extension.ts index dfcfb324..c499d576 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,13 +1,14 @@ "use strict" -import axios from "axios" +import axios, { isAxiosError } from "axios" import { getAuthenticatedUser } from "coder/site/src/api/api" +import { getErrorMessage } from "coder/site/src/api/errors" import fs from "fs" import * as https from "https" import * as module from "module" import * as os from "os" import * as vscode from "vscode" import { Commands } from "./commands" -import { CertificateError } from "./error" +import { CertificateError, getErrorDetail } from "./error" import { Remote } from "./remote" import { Storage } from "./storage" import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider" @@ -199,13 +200,38 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { try { await remote.setup(vscodeProposed.env.remoteAuthority) } catch (ex) { - if (ex instanceof CertificateError) { - return await ex.showModal("Failed to open workspace") + switch (true) { + case ex instanceof CertificateError: + await ex.showModal("Failed to open workspace") + break + case isAxiosError(ex): + { + const msg = getErrorMessage(ex, "") + const detail = getErrorDetail(ex) + const urlString = axios.getUri(ex.response?.config) + let path = urlString + try { + path = new URL(urlString).pathname + } catch (e) { + // ignore, default to full url + } + + await vscodeProposed.window.showErrorMessage("Failed to open workspace", { + detail: `API ${ex.response?.config.method?.toUpperCase()} to '${path}' failed with code ${ex.response?.status}.\nMessage: ${msg}\nDetail: ${detail}`, + modal: true, + useCustom: true, + }) + } + break + default: + await vscodeProposed.window.showErrorMessage("Failed to open workspace", { + detail: (ex as string).toString(), + modal: true, + useCustom: true, + }) } - await vscodeProposed.window.showErrorMessage("Failed to open workspace", { - detail: (ex as string).toString(), - modal: true, - useCustom: true, - }) + + // Always close remote session when we fail to open a workspace. + await remote.closeRemote() } } diff --git a/src/remote.ts b/src/remote.ts index 4f504e79..9b19bee2 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -137,23 +137,33 @@ export class Remote { let buildComplete: undefined | (() => void) if (this.storage.workspace.latest_build.status === "stopped") { + // If the workspace requires the latest active template version, we should attempt + // to update that here. + // TODO: If param set changes, what do we do?? + const versionID = this.storage.workspace.template_require_active_version + ? // Use the latest template version + this.storage.workspace.template_active_version_id + : // Default to not updating the workspace if not required. + this.storage.workspace.latest_build.template_version_id + this.vscodeProposed.window.withProgress( { location: vscode.ProgressLocation.Notification, cancellable: false, - title: "Starting workspace...", + title: this.storage.workspace.template_require_active_version + ? "Updating workspace..." + : "Starting workspace...", }, () => new Promise((r) => { buildComplete = r }), ) + + const latestBuild = await startWorkspace(this.storage.workspace.id, versionID) this.storage.workspace = { ...this.storage.workspace, - latest_build: await startWorkspace( - this.storage.workspace.id, - this.storage.workspace.latest_build.template_version_id, - ), + latest_build: latestBuild, } } @@ -796,7 +806,7 @@ export class Remote { } // closeRemote ends the current remote session. - private async closeRemote() { + public async closeRemote() { await vscode.commands.executeCommand("workbench.action.remote.close") }