From b1bc13eef9b0cae4a744af61aa762785558f9ff4 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 3 Jun 2024 14:18:00 -0800 Subject: [PATCH 1/4] Scope SSH configs by label This lets us create multiple ssh config blocks that each point to different Coder binaries and config directories for those binaries, instead of always overwriting a single configuration block, binary, and config directory with the most recently configured deployment is like we currently do. This scoping is accomplished by changing the ssh config block's comment markers to include the label and by adding the label to the host name: coder-vscode.label--*. The Coder binary splits by -- and ignores the first part, so we are free to use it however we need. This scoping is used support config blocks per deployment, where the label is derived from the deployment URL. That means we can have one config block for dev.coder.com, another for test.coder.com, and so on, each with their own binaries and tokens, coexisting in harmony. --- src/commands.ts | 27 +++++-- src/remote.ts | 21 +++--- src/sshConfig.test.ts | 170 +++++++++++++++++++++++++++++++++++------- src/sshConfig.ts | 58 ++++++++------ src/util.test.ts | 11 +++ src/util.ts | 11 +++ 6 files changed, 231 insertions(+), 67 deletions(-) create mode 100644 src/util.test.ts create mode 100644 src/util.ts diff --git a/src/commands.ts b/src/commands.ts index 4cac38fb..ac140e28 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -7,6 +7,7 @@ import { extractAgents } from "./api-helper" import { CertificateError } from "./error" import { Remote } from "./remote" import { Storage } from "./storage" +import { toSafeHost } from "./util" import { OpenableTreeItem } from "./workspacesProvider" export class Commands { @@ -272,13 +273,19 @@ export class Commands { /** * Open a workspace or agent that is showing in the sidebar. * - * This essentially just builds the host name and passes it to the VS Code - * Remote SSH extension, so it is not necessary to be logged in, although then - * the sidebar would not have any workspaces in it anyway. + * This builds the host name and passes it to the VS Code Remote SSH + * extension. + + * Throw if not logged into a deployment. */ public async openFromSidebar(treeItem: OpenableTreeItem) { if (treeItem) { + const baseUrl = this.restClient.getAxiosInstance().defaults.baseURL + if (!baseUrl) { + throw new Error("You are not logged in") + } await openWorkspace( + baseUrl, treeItem.workspaceOwner, treeItem.workspaceName, treeItem.workspaceAgent, @@ -291,7 +298,7 @@ export class Commands { /** * Open a workspace belonging to the currently logged-in deployment. * - * This must only be called if logged into a deployment. + * Throw if not logged into a deployment. */ public async open(...args: unknown[]): Promise { let workspaceOwner: string @@ -300,6 +307,11 @@ export class Commands { let folderPath: string | undefined let openRecent: boolean | undefined + const baseUrl = this.restClient.getAxiosInstance().defaults.baseURL + if (!baseUrl) { + throw new Error("You are not logged in") + } + if (args.length === 0) { const quickPick = vscode.window.createQuickPick() quickPick.value = "owner:me " @@ -411,7 +423,7 @@ export class Commands { openRecent = args[4] as boolean | undefined } - await openWorkspace(workspaceOwner, workspaceName, workspaceAgent, folderPath, openRecent) + await openWorkspace(baseUrl, workspaceOwner, workspaceName, workspaceAgent, folderPath, openRecent) } /** @@ -439,9 +451,10 @@ export class Commands { /** * Given a workspace, build the host name, find a directory to open, and pass - * both to the Remote SSH plugin. + * both to the Remote SSH plugin in the form of a remote authority URI. */ async function openWorkspace( + baseUrl: string, workspaceOwner: string, workspaceName: string, workspaceAgent: string | undefined, @@ -450,7 +463,7 @@ async function openWorkspace( ) { // A workspace can have multiple agents, but that's handled // when opening a workspace unless explicitly specified. - let remoteAuthority = `ssh-remote+${Remote.Prefix}${workspaceOwner}--${workspaceName}` + let remoteAuthority = `ssh-remote+${Remote.Prefix}.${toSafeHost(baseUrl)}--${workspaceOwner}--${workspaceName}` if (workspaceAgent) { remoteAuthority += `--${workspaceAgent}` } diff --git a/src/remote.ts b/src/remote.ts index 2e7f5194..41a76fdf 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -14,16 +14,17 @@ import * as ws from "ws" import { makeCoderSdk } from "./api" import { Commands } from "./commands" import { getHeaderCommand } from "./headers" -import { SSHConfig, SSHValues, defaultSSHConfigResponse, mergeSSHConfigValues } from "./sshConfig" +import { SSHConfig, SSHValues, mergeSSHConfigValues } from "./sshConfig" import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport" import { Storage } from "./storage" +import { toSafeHost } from "./util" import { supportsCoderAgentLogDirFlag } from "./version" import { WorkspaceAction } from "./workspaceAction" export class Remote { // Prefix is a magic string that is prepended to SSH hosts to indicate that // they should be handled by this extension. - public static readonly Prefix = "coder-vscode--" + public static readonly Prefix = "coder-vscode" public constructor( private readonly vscodeProposed: typeof vscode, @@ -42,8 +43,9 @@ export class Remote { } const sshAuthority = authorityParts[1].substring(Remote.Prefix.length) - // Authorities are in the format: - // coder-vscode------ + // Authorities are in one of two formats: + // coder-vscode------ (old style) + // coder-vscode.