diff --git a/src/commands.ts b/src/commands.ts index 3506d822..d24df729 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -6,7 +6,7 @@ import { makeCoderSdk, needToken } from "./api" import { extractAgents } from "./api-helper" import { CertificateError } from "./error" import { Storage } from "./storage" -import { AuthorityPrefix, toSafeHost } from "./util" +import { toRemoteAuthority, toSafeHost } from "./util" import { OpenableTreeItem } from "./workspacesProvider" export class Commands { @@ -499,6 +499,26 @@ export class Commands { await openWorkspace(baseUrl, workspaceOwner, workspaceName, workspaceAgent, folderPath, openRecent) } + /** + * Open a devcontainer from a workspace belonging to the currently logged-in deployment. + * + * Throw if not logged into a deployment. + */ + public async openDevContainer(...args: string[]): Promise { + const baseUrl = this.restClient.getAxiosInstance().defaults.baseURL + if (!baseUrl) { + throw new Error("You are not logged in") + } + + const workspaceOwner = args[0] as string + const workspaceName = args[1] as string + const workspaceAgent = undefined // args[2] is reserved, but we do not support multiple agents yet. + const devContainerName = args[3] as string + const devContainerFolder = args[4] as string + + await openDevContainer(baseUrl, workspaceOwner, workspaceName, workspaceAgent, devContainerName, devContainerFolder) + } + /** * Update the current workspace. If there is no active workspace connection, * this is a no-op. @@ -536,10 +556,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+${AuthorityPrefix}.${toSafeHost(baseUrl)}--${workspaceOwner}--${workspaceName}` - if (workspaceAgent) { - remoteAuthority += `.${workspaceAgent}` - } + const remoteAuthority = toRemoteAuthority(baseUrl, workspaceOwner, workspaceName, workspaceAgent) let newWindow = true // Open in the existing window if no workspaces are open. @@ -598,3 +615,32 @@ async function openWorkspace( reuseWindow: !newWindow, }) } + +async function openDevContainer( + baseUrl: string, + workspaceOwner: string, + workspaceName: string, + workspaceAgent: string | undefined, + devContainerName: string, + devContainerFolder: string, +) { + const remoteAuthority = toRemoteAuthority(baseUrl, workspaceOwner, workspaceName, workspaceAgent) + + const devContainer = Buffer.from(JSON.stringify({ containerName: devContainerName }), "utf-8").toString("hex") + const devContainerAuthority = `attached-container+${devContainer}@${remoteAuthority}` + + let newWindow = true + if (!vscode.workspace.workspaceFolders?.length) { + newWindow = false + } + + await vscode.commands.executeCommand( + "vscode.openFolder", + vscode.Uri.from({ + scheme: "vscode-remote", + authority: devContainerAuthority, + path: devContainerFolder, + }), + newWindow, + ) +} diff --git a/src/extension.ts b/src/extension.ts index e5e2799a..0c04bf18 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -111,6 +111,61 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { await storage.configureCli(toSafeHost(url), url, token) vscode.commands.executeCommand("coder.open", owner, workspace, agent, folder, openRecent) + } else if (uri.path === "/openDevContainer") { + const workspaceOwner = params.get("owner") + const workspaceName = params.get("workspace") + const workspaceAgent = params.get("agent") + const devContainerName = params.get("devContainerName") + const devContainerFolder = params.get("devContainerFolder") + + if (!workspaceOwner) { + throw new Error("workspace owner must be specified as a query parameter") + } + + if (!workspaceName) { + throw new Error("workspace name must be specified as a query parameter") + } + + if (!devContainerName) { + throw new Error("dev container name must be specified as a query parameter") + } + + if (!devContainerFolder) { + throw new Error("dev container folder must be specified as a query parameter") + } + + // We are not guaranteed that the URL we currently have is for the URL + // this workspace belongs to, or that we even have a URL at all (the + // queries will default to localhost) so ask for it if missing. + // Pre-populate in case we do have the right URL so the user can just + // hit enter and move on. + const url = await commands.maybeAskUrl(params.get("url"), storage.getUrl()) + if (url) { + restClient.setHost(url) + await storage.setUrl(url) + } else { + throw new Error("url must be provided or specified as a query parameter") + } + + // If the token is missing we will get a 401 later and the user will be + // prompted to sign in again, so we do not need to ensure it is set now. + // For non-token auth, we write a blank token since the `vscodessh` + // command currently always requires a token file. However, if there is + // a query parameter for non-token auth go ahead and use it anyway; all + // that really matters is the file is created. + const token = needToken() ? params.get("token") : (params.get("token") ?? "") + + // Store on disk to be used by the cli. + await storage.configureCli(toSafeHost(url), url, token) + + vscode.commands.executeCommand( + "coder.openDevContainer", + workspaceOwner, + workspaceName, + workspaceAgent, + devContainerName, + devContainerFolder, + ) } else { throw new Error(`Unknown path ${uri.path}`) } @@ -123,6 +178,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.commands.registerCommand("coder.login", commands.login.bind(commands)) vscode.commands.registerCommand("coder.logout", commands.logout.bind(commands)) vscode.commands.registerCommand("coder.open", commands.open.bind(commands)) + vscode.commands.registerCommand("coder.openDevContainer", commands.openDevContainer.bind(commands)) vscode.commands.registerCommand("coder.openFromSidebar", commands.openFromSidebar.bind(commands)) vscode.commands.registerCommand("coder.workspace.update", commands.updateWorkspace.bind(commands)) vscode.commands.registerCommand("coder.createWorkspace", commands.createWorkspace.bind(commands)) diff --git a/src/util.ts b/src/util.ts index fd5af748..8253f152 100644 --- a/src/util.ts +++ b/src/util.ts @@ -61,6 +61,19 @@ export function parseRemoteAuthority(authority: string): AuthorityParts | null { } } +export function toRemoteAuthority( + baseUrl: string, + workspaceOwner: string, + workspaceName: string, + workspaceAgent: string | undefined, +): string { + let remoteAuthority = `ssh-remote+${AuthorityPrefix}.${toSafeHost(baseUrl)}--${workspaceOwner}--${workspaceName}` + if (workspaceAgent) { + remoteAuthority += `.${workspaceAgent}` + } + return remoteAuthority +} + /** * Given a URL, return the host in a format that is safe to write. */ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy