Skip to content

Commit b1bc13e

Browse files
committed
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.
1 parent 9437ad2 commit b1bc13e

File tree

6 files changed

+231
-67
lines changed

6 files changed

+231
-67
lines changed

src/commands.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { extractAgents } from "./api-helper"
77
import { CertificateError } from "./error"
88
import { Remote } from "./remote"
99
import { Storage } from "./storage"
10+
import { toSafeHost } from "./util"
1011
import { OpenableTreeItem } from "./workspacesProvider"
1112

1213
export class Commands {
@@ -272,13 +273,19 @@ export class Commands {
272273
/**
273274
* Open a workspace or agent that is showing in the sidebar.
274275
*
275-
* This essentially just builds the host name and passes it to the VS Code
276-
* Remote SSH extension, so it is not necessary to be logged in, although then
277-
* the sidebar would not have any workspaces in it anyway.
276+
* This builds the host name and passes it to the VS Code Remote SSH
277+
* extension.
278+
279+
* Throw if not logged into a deployment.
278280
*/
279281
public async openFromSidebar(treeItem: OpenableTreeItem) {
280282
if (treeItem) {
283+
const baseUrl = this.restClient.getAxiosInstance().defaults.baseURL
284+
if (!baseUrl) {
285+
throw new Error("You are not logged in")
286+
}
281287
await openWorkspace(
288+
baseUrl,
282289
treeItem.workspaceOwner,
283290
treeItem.workspaceName,
284291
treeItem.workspaceAgent,
@@ -291,7 +298,7 @@ export class Commands {
291298
/**
292299
* Open a workspace belonging to the currently logged-in deployment.
293300
*
294-
* This must only be called if logged into a deployment.
301+
* Throw if not logged into a deployment.
295302
*/
296303
public async open(...args: unknown[]): Promise<void> {
297304
let workspaceOwner: string
@@ -300,6 +307,11 @@ export class Commands {
300307
let folderPath: string | undefined
301308
let openRecent: boolean | undefined
302309

310+
const baseUrl = this.restClient.getAxiosInstance().defaults.baseURL
311+
if (!baseUrl) {
312+
throw new Error("You are not logged in")
313+
}
314+
303315
if (args.length === 0) {
304316
const quickPick = vscode.window.createQuickPick()
305317
quickPick.value = "owner:me "
@@ -411,7 +423,7 @@ export class Commands {
411423
openRecent = args[4] as boolean | undefined
412424
}
413425

414-
await openWorkspace(workspaceOwner, workspaceName, workspaceAgent, folderPath, openRecent)
426+
await openWorkspace(baseUrl, workspaceOwner, workspaceName, workspaceAgent, folderPath, openRecent)
415427
}
416428

417429
/**
@@ -439,9 +451,10 @@ export class Commands {
439451

440452
/**
441453
* Given a workspace, build the host name, find a directory to open, and pass
442-
* both to the Remote SSH plugin.
454+
* both to the Remote SSH plugin in the form of a remote authority URI.
443455
*/
444456
async function openWorkspace(
457+
baseUrl: string,
445458
workspaceOwner: string,
446459
workspaceName: string,
447460
workspaceAgent: string | undefined,
@@ -450,7 +463,7 @@ async function openWorkspace(
450463
) {
451464
// A workspace can have multiple agents, but that's handled
452465
// when opening a workspace unless explicitly specified.
453-
let remoteAuthority = `ssh-remote+${Remote.Prefix}${workspaceOwner}--${workspaceName}`
466+
let remoteAuthority = `ssh-remote+${Remote.Prefix}.${toSafeHost(baseUrl)}--${workspaceOwner}--${workspaceName}`
454467
if (workspaceAgent) {
455468
remoteAuthority += `--${workspaceAgent}`
456469
}

src/remote.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ import * as ws from "ws"
1414
import { makeCoderSdk } from "./api"
1515
import { Commands } from "./commands"
1616
import { getHeaderCommand } from "./headers"
17-
import { SSHConfig, SSHValues, defaultSSHConfigResponse, mergeSSHConfigValues } from "./sshConfig"
17+
import { SSHConfig, SSHValues, mergeSSHConfigValues } from "./sshConfig"
1818
import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport"
1919
import { Storage } from "./storage"
20+
import { toSafeHost } from "./util"
2021
import { supportsCoderAgentLogDirFlag } from "./version"
2122
import { WorkspaceAction } from "./workspaceAction"
2223

2324
export class Remote {
2425
// Prefix is a magic string that is prepended to SSH hosts to indicate that
2526
// they should be handled by this extension.
26-
public static readonly Prefix = "coder-vscode--"
27+
public static readonly Prefix = "coder-vscode"
2728

2829
public constructor(
2930
private readonly vscodeProposed: typeof vscode,
@@ -42,8 +43,9 @@ export class Remote {
4243
}
4344
const sshAuthority = authorityParts[1].substring(Remote.Prefix.length)
4445

45-
// Authorities are in the format:
46-
// coder-vscode--<username>--<workspace>--<agent>
46+
// Authorities are in one of two formats:
47+
// coder-vscode--<username>--<workspace>--<agent> (old style)
48+
// coder-vscode.<label>--<username>--<workspace>--<agent>
4749
// The agent can be omitted; the user will be prompted for it instead.
4850
const parts = sshAuthority.split("--")
4951
if (parts.length !== 2 && parts.length !== 3) {
@@ -81,6 +83,7 @@ export class Remote {
8183
}
8284

8385
const baseUrl = new URL(baseUrlRaw)
86+
const safeHost = toSafeHost(baseUrlRaw) // Deployment label.
8487
const token = await this.storage.getSessionToken()
8588
const restClient = await makeCoderSdk(baseUrlRaw, token, this.storage)
8689
// Store for use in commands.
@@ -509,7 +512,7 @@ export class Remote {
509512
// If we didn't write to the SSH config file, connecting would fail with
510513
// "Host not found".
511514
try {
512-
await this.updateSSHConfig(restClient, authorityParts[1], hasCoderLogs)
515+
await this.updateSSHConfig(restClient, safeHost, authorityParts[1], hasCoderLogs)
513516
} catch (error) {
514517
this.storage.writeToCoderOutputChannel(`Failed to configure SSH: ${error}`)
515518
throw error
@@ -544,8 +547,8 @@ export class Remote {
544547

545548
// updateSSHConfig updates the SSH configuration with a wildcard that handles
546549
// all Coder entries.
547-
private async updateSSHConfig(restClient: Api, hostName: string, hasCoderLogs = false) {
548-
let deploymentSSHConfig = defaultSSHConfigResponse
550+
private async updateSSHConfig(restClient: Api, label: string, hostName: string, hasCoderLogs = false) {
551+
let deploymentSSHConfig = {}
549552
try {
550553
const deploymentConfig = await restClient.getDeploymentSSHConfig()
551554
deploymentSSHConfig = deploymentConfig.ssh_config_options
@@ -641,7 +644,7 @@ export class Remote {
641644
logArg = ` --log-dir ${escape(this.storage.getLogPath())}`
642645
}
643646
const sshValues: SSHValues = {
644-
Host: `${Remote.Prefix}*`,
647+
Host: label ? `${Remote.Prefix}.${label}--*` : `${RemotePrefix}--*`,
645648
ProxyCommand: `${escape(binaryPath)}${headerArg} vscodessh --network-info-dir ${escape(
646649
this.storage.getNetworkInfoPath(),
647650
)}${logArg} --session-token-file ${escape(this.storage.getSessionTokenPath())} --url-file ${escape(
@@ -658,7 +661,7 @@ export class Remote {
658661
sshValues.SetEnv = " CODER_SSH_SESSION_TYPE=vscode"
659662
}
660663

661-
await sshConfig.update(sshValues, sshConfigOverrides)
664+
await sshConfig.update(label, sshValues, sshConfigOverrides)
662665

663666
// A user can provide a "Host *" entry in their SSH config to add options
664667
// to all hosts. We need to ensure that the options we set are not

0 commit comments

Comments
 (0)