Skip to content

Commit

Permalink
Use cli version for featureset detection (#328)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcpeinhardt authored Jul 26, 2024
1 parent 7e70e95 commit c6e7f36
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 50 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,5 +304,6 @@
"semver": "7.6.2",
"trim": "0.0.3",
"word-wrap": "1.2.5"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
14 changes: 14 additions & 0 deletions src/featureSet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as semver from "semver"
import { describe, expect, it } from "vitest"
import { featureSetForVersion } from "./featureSet"

describe("check version support", () => {
it("has logs", () => {
;["v1.3.3+e491217", "v2.3.3+e491217"].forEach((v: string) => {
expect(featureSetForVersion(semver.parse(v)).proxyLogDirectory).toBeFalsy()
})
;["v2.3.4+e491217", "v5.3.4+e491217", "v5.0.4+e491217"].forEach((v: string) => {
expect(featureSetForVersion(semver.parse(v)).proxyLogDirectory).toBeTruthy()
})
})
})
25 changes: 25 additions & 0 deletions src/featureSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as semver from "semver"

export type FeatureSet = {
vscodessh: boolean
proxyLogDirectory: boolean
}

/**
* Builds and returns a FeatureSet object for a given coder version.
*/
export function featureSetForVersion(version: semver.SemVer | null): FeatureSet {
return {
vscodessh: !(
version?.major === 0 &&
version?.minor <= 14 &&
version?.patch < 1 &&
version?.prerelease.length === 0
),

// CLI versions before 2.3.3 don't support the --log-dir flag!
// If this check didn't exist, VS Code connections would fail on
// older versions because of an unknown CLI argument.
proxyLogDirectory: (version?.compare("2.3.3") || 0) > 0 || version?.prerelease[0] === "devel",
}
}
66 changes: 38 additions & 28 deletions src/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import * as semver from "semver"
import * as vscode from "vscode"
import { makeCoderSdk, startWorkspace, waitForBuild } from "./api"
import { extractAgents } from "./api-helper"
import * as cli from "./cliManager"
import { Commands } from "./commands"
import { featureSetForVersion, FeatureSet } from "./featureSet"
import { getHeaderCommand } from "./headers"
import { SSHConfig, SSHValues, mergeSSHConfigValues } from "./sshConfig"
import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport"
import { Storage } from "./storage"
import { AuthorityPrefix, expandPath, parseRemoteAuthority } from "./util"
import { supportsCoderAgentLogDirFlag } from "./version"
import { WorkspaceAction } from "./workspaceAction"

export interface RemoteDetails extends vscode.Disposable {
Expand All @@ -33,7 +34,6 @@ export class Remote {
private readonly storage: Storage,
private readonly commands: Commands,
private readonly mode: vscode.ExtensionMode,
private coderVersion: semver.SemVer | null = null,
) {}

private async confirmStart(workspaceName: string): Promise<boolean> {
Expand Down Expand Up @@ -194,16 +194,34 @@ export class Remote {
// Store for use in commands.
this.commands.workspaceRestClient = workspaceRestClient

let binaryPath: string | undefined
if (this.mode === vscode.ExtensionMode.Production) {
binaryPath = await this.storage.fetchBinary(workspaceRestClient, parts.label)
} else {
try {
// In development, try to use `/tmp/coder` as the binary path.
// This is useful for debugging with a custom bin!
binaryPath = path.join(os.tmpdir(), "coder")
await fs.stat(binaryPath)
} catch (ex) {
binaryPath = await this.storage.fetchBinary(workspaceRestClient, parts.label)
}
}

// First thing is to check the version.
const buildInfo = await workspaceRestClient.getBuildInfo()
this.coderVersion = semver.parse(buildInfo.version)

let version: semver.SemVer | null = null
try {
version = semver.parse(await cli.version(binaryPath))
} catch (e) {
version = semver.parse(buildInfo.version)
}

const featureSet = featureSetForVersion(version)

// Server versions before v0.14.1 don't support the vscodessh command!
if (
this.coderVersion?.major === 0 &&
this.coderVersion?.minor <= 14 &&
this.coderVersion?.patch < 1 &&
this.coderVersion?.prerelease.length === 0
) {
if (!featureSet.vscodessh) {
await this.vscodeProposed.window.showErrorMessage(
"Incompatible Server",
{
Expand Down Expand Up @@ -501,7 +519,7 @@ export class Remote {
// "Host not found".
try {
this.storage.writeToCoderOutputChannel("Updating SSH config...")
await this.updateSSHConfig(workspaceRestClient, parts.label, parts.host)
await this.updateSSHConfig(workspaceRestClient, parts.label, parts.host, binaryPath, featureSet)
} catch (error) {
this.storage.writeToCoderOutputChannel(`Failed to configure SSH: ${error}`)
throw error
Expand Down Expand Up @@ -544,8 +562,8 @@ export class Remote {
/**
* Format's the --log-dir argument for the ProxyCommand
*/
private async formatLogArg(): Promise<string> {
if (!supportsCoderAgentLogDirFlag(this.coderVersion)) {
private async formatLogArg(featureSet: FeatureSet): Promise<string> {
if (!featureSet.proxyLogDirectory) {
return ""
}

Expand All @@ -563,7 +581,13 @@ export class Remote {

// updateSSHConfig updates the SSH configuration with a wildcard that handles
// all Coder entries.
private async updateSSHConfig(restClient: Api, label: string, hostName: string) {
private async updateSSHConfig(
restClient: Api,
label: string,
hostName: string,
binaryPath: string,
featureSet: FeatureSet,
) {
let deploymentSSHConfig = {}
try {
const deploymentConfig = await restClient.getDeploymentSSHConfig()
Expand Down Expand Up @@ -624,20 +648,6 @@ export class Remote {
const sshConfig = new SSHConfig(sshConfigFile)
await sshConfig.load()

let binaryPath: string | undefined
if (this.mode === vscode.ExtensionMode.Production) {
binaryPath = await this.storage.fetchBinary(restClient, label)
} else {
try {
// In development, try to use `/tmp/coder` as the binary path.
// This is useful for debugging with a custom bin!
binaryPath = path.join(os.tmpdir(), "coder")
await fs.stat(binaryPath)
} catch (ex) {
binaryPath = await this.storage.fetchBinary(restClient, label)
}
}

const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"`
// Escape a command line to be executed by the Coder binary, so ssh doesn't substitute variables.
const escapeSubcommand: (str: string) => string =
Expand All @@ -659,7 +669,7 @@ export class Remote {
Host: label ? `${AuthorityPrefix}.${label}--*` : `${AuthorityPrefix}--*`,
ProxyCommand: `${escape(binaryPath)}${headerArg} vscodessh --network-info-dir ${escape(
this.storage.getNetworkInfoPath(),
)}${await this.formatLogArg()} --session-token-file ${escape(this.storage.getSessionTokenPath(label))} --url-file ${escape(
)}${await this.formatLogArg(featureSet)} --session-token-file ${escape(this.storage.getSessionTokenPath(label))} --url-file ${escape(
this.storage.getUrlPath(label),
)} %h`,
ConnectTimeout: "0",
Expand Down
13 changes: 0 additions & 13 deletions src/version.test.ts

This file was deleted.

8 changes: 0 additions & 8 deletions src/version.ts

This file was deleted.

0 comments on commit c6e7f36

Please sign in to comment.