镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content
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
4 changes: 4 additions & 0 deletions src/featureSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ export interface CliFeatureSet {
supportBundle: boolean;
supportBundleWorkspaceFiles: boolean;
allowRedirects: boolean;
customSessionAppNames: boolean;
}

/** Capabilities keyed to the Coder server (REST API) version. */
export interface ServerFeatureSet {
tasks: boolean;
onSuccessBuild: boolean;
customSessionAppNames: boolean;
}

/**
Expand Down Expand Up @@ -58,6 +60,7 @@ export function cliFeatureSet(version: semver.SemVer | null): CliFeatureSet {
supportBundleWorkspaceFiles: versionAtLeast(version, "2.36.0"),
// --allow-redirects; from 2.38 the CLI otherwise errors on a redirected URL.
allowRedirects: versionAtLeast(version, "2.38.0"),
customSessionAppNames: versionAtLeast(version, "2.38.0"),
};
}

Expand All @@ -71,5 +74,6 @@ export function serverFeatureSet(
versionAtLeast(version, "2.29.0") && !versionAtLeast(version, "2.35.0"),
// `on_success` on a stop build, which queues the start in one request
onSuccessBuild: versionAtLeast(version, "2.36.0"),
customSessionAppNames: versionAtLeast(version, "2.38.0"),
};
}
88 changes: 48 additions & 40 deletions src/remote/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ interface RemoteSetupContext {
disposables: vscode.Disposable[];
}

interface SshConfigOptions {
workspaceClient: Api;
parts: AuthorityParts;
binaryPath: string;
logDir: string;
cliFeatures: CliFeatureSet;
cliAuth: CliAuth;
sessionApp: string;
}

/**
* What Open Recent shows after the path. VS Code splits the label on the
* separator, so "/" would display "/home/kyle [Coder: kyle/workspace]" as
Expand Down Expand Up @@ -310,16 +320,15 @@ export class Remote {
this.resolveRemoteBinary(workspaceClient),
);

const { cliFeatures, serverFeatures, cliAuth } = await tracer.phase(
"compatibility_check",
() =>
const { cliFeatures, serverFeatures, cliAuth, sessionApp } =
await tracer.phase("compatibility_check", () =>
this.checkCompatibility({
workspaceClient,
binaryPath,
baseUrl,
safeHostname: parts.safeHostname,
}),
);
);

// Reject deployments below our minimum supported version (v0.25.0)
// before configuring credentials, so they get a clear message.
Expand Down Expand Up @@ -426,14 +435,15 @@ export class Remote {
const logDir = this.getLogDir(cliFeatures);

const computedSshProperties = await tracer.phase("ssh_config_write", () =>
this.writeRemoteSshConfig(
context,
this.writeRemoteSshConfig({
parts: context.parts,
workspaceClient,
binaryPath,
logDir,
cliFeatures,
cliAuth,
),
sessionApp,
}),
);
const remoteCommand = computedSshProperties.remotecommand;

Expand Down Expand Up @@ -689,23 +699,11 @@ export class Remote {
}

private async writeRemoteSshConfig(
context: RemoteSetupContext,
workspaceClient: Api,
binaryPath: string,
logDir: string,
cliFeatures: CliFeatureSet,
cliAuth: CliAuth,
options: SshConfigOptions,
Comment thread
EhabY marked this conversation as resolved.
): Promise<SshProperties> {
try {
this.logger.info("Updating SSH config...");
return await this.updateSSHConfig(
workspaceClient,
context.parts,
binaryPath,
logDir,
cliFeatures,
cliAuth,
);
return await this.updateSSHConfig(options);
} catch (error) {
this.logger.warn("Failed to configure SSH", error);
throw error;
Expand Down Expand Up @@ -779,19 +777,27 @@ export class Remote {
cliFeatures: CliFeatureSet;
serverFeatures: ServerFeatureSet;
cliAuth: CliAuth;
sessionApp: string;
}> {
const { workspaceClient, binaryPath, baseUrl, safeHostname } = options;
const buildInfo = await workspaceClient.getBuildInfo();
const serverVersion = semver.parse(buildInfo.version);

let version: semver.SemVer | null;
let cliFeatures: CliFeatureSet;
try {
version = semver.parse(await cliVersion(binaryPath));
cliFeatures = cliFeatureSet(semver.parse(await cliVersion(binaryPath)));
} catch {
version = serverVersion;
cliFeatures = {
...cliFeatureSet(serverVersion),
customSessionAppNames: false,
};
}

const cliFeatures = cliFeatureSet(version);
const serverFeatures = serverFeatureSet(serverVersion);
// Report schemes verbatim; normalization and editor families belong to the server.
const sessionApp =
cliFeatures.customSessionAppNames && serverFeatures.customSessionAppNames
? vscode.env.uriScheme || "vscode"
: "vscode";
const configDir = this.pathResolver.getGlobalConfigDir(safeHostname);
const cliAuth = resolveCliAuth(
vscode.workspace.getConfiguration(),
Expand All @@ -801,8 +807,9 @@ export class Remote {
);
return {
cliFeatures,
serverFeatures: serverFeatureSet(serverVersion),
serverFeatures,
cliAuth,
sessionApp,
};
}

Expand Down Expand Up @@ -868,14 +875,15 @@ export class Remote {

// updateSSHConfig updates the SSH configuration with a wildcard that handles
// all Coder entries.
private async updateSSHConfig(
restClient: Api,
parts: AuthorityParts,
binaryPath: string,
logDir: string,
cliFeatures: CliFeatureSet,
cliAuth: CliAuth,
): Promise<SshProperties> {
private async updateSSHConfig({
workspaceClient,
parts,
binaryPath,
logDir,
cliFeatures,
cliAuth,
sessionApp,
}: SshConfigOptions): Promise<SshProperties> {
// Taken from the authority, so a legacy host keeps working.
const { hostPrefix, safeHostname, sshHost } = parts;
// One file per (host prefix, deployment); the user's config gains one shared include.
Expand All @@ -901,7 +909,7 @@ export class Remote {

let deploymentSshConfig = {};
try {
const deploymentConfig = await restClient.getDeploymentSSHConfig();
const deploymentConfig = await workspaceClient.getDeploymentSSHConfig();
deploymentSshConfig = validateDeploymentSshOptions(
deploymentConfig.ssh_config_options,
{ ...configSshOptions, ...userConfig },
Expand Down Expand Up @@ -936,6 +944,7 @@ export class Remote {
binaryPath,
cliAuth,
logArgs: await this.getLogArgs(logDir),
sessionApp,
};
const proxyCommand = cliFeatures.wildcardSSH
? buildSshProxyCommand({ ...proxyOptions, hostPrefix })
Expand All @@ -952,9 +961,7 @@ export class Remote {
ServerAliveCountMax: "3",
};
if (sshSupportsSetEnv()) {
// This allows for tracking the number of extension
// users connected to workspaces!
sshValues.SetEnv = "CODER_SSH_SESSION_TYPE=vscode";
sshValues.SetEnv = `CODER_SSH_SESSION_TYPE=${sessionApp}`;
}

// Write our file before including it, so the include never dangles.
Expand Down Expand Up @@ -1095,6 +1102,7 @@ interface ProxyCommandOptions {
binaryPath: string;
cliAuth: CliAuth;
logArgs: string[];
sessionApp: string;
}

function coderCommand(
Expand All @@ -1116,7 +1124,7 @@ export function buildSshProxyCommand(
// Make sure to update the `coder.sshFlags` description if we add more internal flags here!
const internalFlags = [
"--stdio",
"--usage-app=vscode",
`--usage-app=${escapeCommandArg(options.sessionApp)}`,
"--network-info-dir",
escapeCommandArg(options.pathResolver.getNetworkInfoPath()),
...options.logArgs,
Expand Down
2 changes: 2 additions & 0 deletions test/unit/api/workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const CLI_FEATURES: CliFeatureSet = {
supportBundle: true,
supportBundleWorkspaceFiles: true,
allowRedirects: true,
customSessionAppNames: true,
};

function mockStream(): UnidirectionalStream<unknown> {
Expand Down Expand Up @@ -110,6 +111,7 @@ function createUpdateCtx(
serverFeatures: {
tasks: false,
onSuccessBuild: false,
customSessionAppNames: false,
...overrides.serverFeatures,
},
};
Expand Down
12 changes: 12 additions & 0 deletions test/unit/featureSet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ describe("check version support", () => {
}
});

it.each([
["2.37.99", false],
["2.38.0-rc.1", false],
["2.38.0", true],
["0.0.0-devel+abc123", true],
["invalid", false],
] as const)("custom session app names for %s: %s", (version, expected) => {
const parsed = semver.parse(version);
expect(cliFeatureSet(parsed).customSessionAppNames).toBe(expected);
expect(serverFeatureSet(parsed).customSessionAppNames).toBe(expected);
});

it("enables all features for development builds", () => {
const featureSet = cliFeatureSet(semver.parse("v0.0.0-devel+abc123"));

Expand Down
Loading
Loading