From c6e7f3630b3bf51c0cd4719dc3c55d1d913dde9f Mon Sep 17 00:00:00 2001 From: Benjamin Peinhardt <61021968+bcpeinhardt@users.noreply.github.com> Date: Fri, 26 Jul 2024 12:10:07 -0500 Subject: [PATCH] Use cli version for featureset detection (#328) --- package.json | 3 +- src/featureSet.test.ts | 14 +++++++++ src/featureSet.ts | 25 ++++++++++++++++ src/remote.ts | 66 ++++++++++++++++++++++++------------------ src/version.test.ts | 13 --------- src/version.ts | 8 ----- 6 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 src/featureSet.test.ts create mode 100644 src/featureSet.ts delete mode 100644 src/version.test.ts delete mode 100644 src/version.ts diff --git a/package.json b/package.json index 0d4c6b18..321a6659 100644 --- a/package.json +++ b/package.json @@ -304,5 +304,6 @@ "semver": "7.6.2", "trim": "0.0.3", "word-wrap": "1.2.5" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/featureSet.test.ts b/src/featureSet.test.ts new file mode 100644 index 00000000..4fa594ce --- /dev/null +++ b/src/featureSet.test.ts @@ -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() + }) + }) +}) diff --git a/src/featureSet.ts b/src/featureSet.ts new file mode 100644 index 00000000..62ff0c2b --- /dev/null +++ b/src/featureSet.ts @@ -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", + } +} diff --git a/src/remote.ts b/src/remote.ts index 415f1731..07fb94bb 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -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 { @@ -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 { @@ -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", { @@ -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 @@ -544,8 +562,8 @@ export class Remote { /** * Format's the --log-dir argument for the ProxyCommand */ - private async formatLogArg(): Promise { - if (!supportsCoderAgentLogDirFlag(this.coderVersion)) { + private async formatLogArg(featureSet: FeatureSet): Promise { + if (!featureSet.proxyLogDirectory) { return "" } @@ -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() @@ -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 = @@ -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", diff --git a/src/version.test.ts b/src/version.test.ts deleted file mode 100644 index c9cc71e6..00000000 --- a/src/version.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { parse } from "semver" -import { describe, expect, it } from "vitest" -import { supportsCoderAgentLogDirFlag } from "./version" - -describe("check version support", () => { - it("has logs", () => { - expect(supportsCoderAgentLogDirFlag(parse("v1.3.3+e491217"))).toBeFalsy() - expect(supportsCoderAgentLogDirFlag(parse("v2.3.3+e491217"))).toBeFalsy() - expect(supportsCoderAgentLogDirFlag(parse("v2.3.4+e491217"))).toBeTruthy() - expect(supportsCoderAgentLogDirFlag(parse("v5.3.4+e491217"))).toBeTruthy() - expect(supportsCoderAgentLogDirFlag(parse("v5.0.4+e491217"))).toBeTruthy() - }) -}) diff --git a/src/version.ts b/src/version.ts deleted file mode 100644 index d4a2199b..00000000 --- a/src/version.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SemVer } from "semver" - -// 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. -export const supportsCoderAgentLogDirFlag = (ver: SemVer | null): boolean => { - return (ver?.compare("2.3.3") || 0) > 0 || ver?.prerelease[0] === "devel" -}