Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@
"node-forge": "^1.3.1",
"pretty-bytes": "^6.1.1",
"proxy-agent": "^6.4.0",
"range_check": "^3.2.0",
"semver": "^7.6.2",
"ua-parser-js": "^1.0.38",
"ws": "^8.18.1",
Expand Down
105 changes: 92 additions & 13 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { isAxiosError } from "axios"
import { Api } from "coder/site/src/api/api"
import { getErrorMessage } from "coder/site/src/api/errors"
import { User, Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated"
import { lookup } from "dns"
import { inRange } from "range_check"
import { promisify } from "util"
import * as vscode from "vscode"
import { makeCoderSdk, needToken } from "./api"
import { extractAgents } from "./api-helper"
Expand Down Expand Up @@ -392,14 +396,26 @@ export class Commands {
if (!baseUrl) {
throw new Error("You are not logged in")
}
await openWorkspace(
baseUrl,
treeItem.workspaceOwner,
treeItem.workspaceName,
treeItem.workspaceAgent,
treeItem.workspaceFolderPath,
true,
)

try {
await openWorkspace(
this.restClient,
baseUrl,
treeItem.workspaceOwner,
treeItem.workspaceName,
treeItem.workspaceAgent,
treeItem.workspaceFolderPath,
true,
)
} catch (err) {
const message = getErrorMessage(err, "no response from the server")
this.storage.writeToCoderOutputChannel(`Failed to open workspace: ${message}`)
this.vscodeProposed.window.showErrorMessage("Failed to open workspace", {
detail: message,
modal: true,
useCustom: true,
})
}
} else {
// If there is no tree item, then the user manually ran this command.
// Default to the regular open instead.
Expand Down Expand Up @@ -491,12 +507,30 @@ export class Commands {
} else {
workspaceOwner = args[0] as string
workspaceName = args[1] as string
// workspaceAgent is reserved for args[2], but multiple agents aren't supported yet.
workspaceAgent = args[2] as string
Copy link
Member Author

@ethanndickson ethanndickson Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URI handler does indeed populate the agent name in args[2], but it may be undefined.

vscode.commands.executeCommand("coder.open", owner, workspace, agent, folder, openRecent)

The original comment is years out of date.

folderPath = args[3] as string | undefined
openRecent = args[4] as boolean | undefined
}

await openWorkspace(baseUrl, workspaceOwner, workspaceName, workspaceAgent, folderPath, openRecent)
try {
await openWorkspace(
this.restClient,
baseUrl,
workspaceOwner,
workspaceName,
workspaceAgent,
folderPath,
openRecent,
)
} catch (err) {
const message = getErrorMessage(err, "no response from the server")
this.storage.writeToCoderOutputChannel(`Failed to open workspace: ${message}`)
this.vscodeProposed.window.showErrorMessage("Failed to open workspace", {
detail: message,
modal: true,
useCustom: true,
})
}
}

/**
Expand Down Expand Up @@ -547,16 +581,46 @@ export class Commands {
* both to the Remote SSH plugin in the form of a remote authority URI.
*/
async function openWorkspace(
restClient: Api,
baseUrl: string,
workspaceOwner: string,
workspaceName: string,
workspaceAgent: string | undefined,
folderPath: string | undefined,
openRecent: boolean | undefined,
) {
// A workspace can have multiple agents, but that's handled
// when opening a workspace unless explicitly specified.
const remoteAuthority = toRemoteAuthority(baseUrl, workspaceOwner, workspaceName, workspaceAgent)
let remoteAuthority = toRemoteAuthority(baseUrl, workspaceOwner, workspaceName, workspaceAgent)

// When called from `openFromSidebar`, the workspaceAgent will only not be set
// if the workspace is stopped, in which case we can't use Coder Connect
// When called from `open`, the workspaceAgent will always be set.
if (workspaceAgent) {
let hostnameSuffix = "coder"
try {
const sshConfig = await restClient.getDeploymentSSHConfig()
// If the field is undefined, it's an older server, and always 'coder'
hostnameSuffix = sshConfig.hostname_suffix ?? hostnameSuffix
} catch (error) {
if (!isAxiosError(error)) {
throw error
}
switch (error.response?.status) {
case 404: {
// Likely a very old deployment, just use the default.
break
}
case 401: {
throw error
}
default:
throw error
}
}
const coderConnectAddr = await maybeCoderConnectAddr(workspaceAgent, workspaceName, workspaceOwner, hostnameSuffix)
if (coderConnectAddr) {
remoteAuthority = `ssh-remote+${coderConnectAddr}`
}
}

let newWindow = true
// Open in the existing window if no workspaces are open.
Expand Down Expand Up @@ -616,6 +680,21 @@ async function openWorkspace(
})
}

async function maybeCoderConnectAddr(
agent: string,
workspace: string,
owner: string,
hostnameSuffix: string,
): Promise<string | undefined> {
const coderConnectHostname = `${agent}.${workspace}.${owner}.${hostnameSuffix}`
try {
const res = await promisify(lookup)(coderConnectHostname)
return res.family === 6 && inRange(res.address, "fd60:627a:a42b::/48") ? coderConnectHostname : undefined
} catch {
return undefined
}
}

async function openDevContainer(
baseUrl: string,
workspaceOwner: string,
Expand Down
2 changes: 1 addition & 1 deletion src/workspacesProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ export class WorkspaceTreeItem extends OpenableTreeItem {
showOwner ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.Expanded,
workspace.owner_name,
workspace.name,
undefined,
agents.length > 0 ? agents[0].name : undefined,
agents[0]?.expanded_directory,
agents.length > 1 ? "coderWorkspaceMultipleAgents" : "coderWorkspaceSingleAgent",
)
Expand Down
20 changes: 19 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1749,7 +1749,7 @@ [email protected]:

"coder@https://github.com/coder/coder#main":
version "0.0.0"
resolved "https://github.com/coder/coder#3ac844ad3d341d2910542b83d4f33df7bd0be85e"
resolved "https://github.com/coder/coder#f8971bb3cc01d81b3085b2b3c9253d8d340d125c"

collapse-white-space@^1.0.2:
version "1.0.6"
Expand Down Expand Up @@ -3441,6 +3441,16 @@ ip-address@^9.0.5:
jsbn "1.1.0"
sprintf-js "^1.1.3"

ip6@^0.2.10:
version "0.2.11"
resolved "https://registry.yarnpkg.com/ip6/-/ip6-0.2.11.tgz#b7cf71864ef16c7418c29f7b1f2f5db892a189ec"
integrity sha512-OmTP7FyIp+ZoNvZ7Xr97bWrCgypa3BeuYuRFNTOPT8Y11cxMW1pW1VC70kHZP1onSHHMotADcjdg5QyECiIMUw==

ipaddr.js@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8"
integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==

irregular-plurals@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-2.0.0.tgz#39d40f05b00f656d0b7fa471230dd3b714af2872"
Expand Down Expand Up @@ -4834,6 +4844,14 @@ randombytes@^2.1.0:
dependencies:
safe-buffer "^5.1.0"

range_check@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/range_check/-/range_check-3.2.0.tgz#6ef17940bb382a7fb905ecda8204f2f28ce7f61d"
integrity sha512-JxiMqvzQJJLt5vaKSUm7f++UkDM1TuMbkQsqRZJYaSvvCTTVtoUMkE/rm+ZNgLXNFAQPhO74WgMPHJaxz/JOEA==
dependencies:
ip6 "^0.2.10"
ipaddr.js "^2.2.0"

rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
Expand Down