Skip to content

Add support for connections to multiple deployments #292

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
42 changes: 33 additions & 9 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import * as vscode from "vscode"
import { makeCoderSdk } from "./api"
import { extractAgents } from "./api-helper"
import { CertificateError } from "./error"
import { Remote } from "./remote"
import { Storage } from "./storage"
import { AuthorityPrefix, toSafeHost } from "./util"
import { OpenableTreeItem } from "./workspacesProvider"

export class Commands {
Expand Down Expand Up @@ -153,10 +153,13 @@ export class Commands {
this.restClient.setHost(url)
this.restClient.setSessionToken(token)

// Store these to be used in later sessions and in the cli.
// Store these to be used in later sessions.
await this.storage.setURL(url)
await this.storage.setSessionToken(token)

// Store on disk to be used by the cli.
await this.storage.configureCli(toSafeHost(url), url, token)

await vscode.commands.executeCommand("setContext", "coder.authenticated", true)
if (user.roles.find((role) => role.name === "owner")) {
await vscode.commands.executeCommand("setContext", "coder.isOwner", true)
Expand Down Expand Up @@ -197,6 +200,12 @@ export class Commands {
* Log out from the currently logged-in deployment.
*/
public async logout(): Promise<void> {
const url = this.storage.getUrl()
if (!url) {
// Sanity check; command should not be available if no url.
throw new Error("You are not logged in")
}

// Clear from the REST client. An empty url will indicate to other parts of
// the code that we are logged out.
this.restClient.setHost("")
Expand All @@ -206,6 +215,9 @@ export class Commands {
await this.storage.setURL(undefined)
await this.storage.setSessionToken(undefined)

// Clear from disk.
await this.storage.configureCli(toSafeHost(url), undefined, undefined)

await vscode.commands.executeCommand("setContext", "coder.authenticated", false)
vscode.window.showInformationMessage("You've been logged out of Coder!", "Login").then((action) => {
if (action === "Login") {
Expand Down Expand Up @@ -272,13 +284,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,
Expand All @@ -291,7 +309,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<void> {
let workspaceOwner: string
Expand All @@ -300,6 +318,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 "
Expand Down Expand Up @@ -411,7 +434,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)
}

/**
Expand Down Expand Up @@ -439,9 +462,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,
Expand All @@ -450,7 +474,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+${AuthorityPrefix}.${toSafeHost(baseUrl)}--${workspaceOwner}--${workspaceName}`
if (workspaceAgent) {
remoteAuthority += `--${workspaceAgent}`
}
Expand Down
6 changes: 6 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Commands } from "./commands"
import { CertificateError, getErrorDetail } from "./error"
import { Remote } from "./remote"
import { Storage } from "./storage"
import { toSafeHost } from "./util"
import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider"

export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
Expand Down Expand Up @@ -108,6 +109,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
// 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")
Expand All @@ -117,9 +119,13 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
// prompted to sign in again, so we do not need to ensure it is set.
const token = params.get("token")
if (token) {
restClient.setSessionToken(token)
await storage.setSessionToken(token)
}

// Store on disk to be used by the cli.
await storage.configureCli(toSafeHost(url), url, token)
Copy link
Member

Choose a reason for hiding this comment

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

I've noticed that all the calls to storage.configureCli have also required a call to toSafeHost. Do you think that configureCli could call toSafeHost internally?

Copy link
Member Author

@code-asher code-asher Jun 4, 2024

Choose a reason for hiding this comment

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

That is a good question. My thinking is that this storage interface is taking a directory name (or label as I am calling it), so from that perspective it does not make too much sense for it to be aware of URLs and how to encode them.

So, for similar reasons that, say, fs.writeFile(fileName) would not automatically encode the file name (er, at least, pretty sure it does not).

Similar to how SSHConfig takes a label rather than a URL as well.

Copy link
Member Author

@code-asher code-asher Jun 4, 2024

Choose a reason for hiding this comment

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

Probably it will always be a URL, but in the future there could be tests doing things like storage.configureCli("temp-dir") or some such. But you could argue that the tests should just use URLs too, and the storage abstraction should change to take URLs rather than generic labels (and SSHConfig as well).

But, I am partial to the generic label abstraction personally, maybe because it feels less coupled/simpler to me.

I would have tests already except we need to switch away from vitest back to the VS Code testing framework. 😭


vscode.commands.executeCommand("coder.open", owner, workspace, agent, folder, openRecent)
}
},
Expand Down
Loading