Skip to content

Commit cfb7593

Browse files
committed
Move CLI configuration from storage.ts
1 parent 0302ead commit cfb7593

File tree

6 files changed

+117
-113
lines changed

6 files changed

+117
-113
lines changed

src/commands.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as vscode from "vscode";
99
import { createWorkspaceIdentifier, extractAgents } from "./api/api-helper";
1010
import { CoderApi } from "./api/coderApi";
1111
import { needToken } from "./api/utils";
12+
import { CliConfigManager } from "./core/cliConfig";
1213
import { PathResolver } from "./core/pathResolver";
1314
import { CertificateError } from "./error";
1415
import { getGlobalFlags } from "./globalFlags";
@@ -21,6 +22,7 @@ import {
2122
} from "./workspacesProvider";
2223

2324
export class Commands {
25+
private readonly cliConfigManager: CliConfigManager;
2426
// These will only be populated when actively connected to a workspace and are
2527
// used in commands. Because commands can be executed by the user, it is not
2628
// possible to pass in arguments, so we have to store the current workspace
@@ -37,7 +39,9 @@ export class Commands {
3739
private readonly restClient: Api,
3840
private readonly storage: Storage,
3941
private readonly pathResolver: PathResolver,
40-
) {}
42+
) {
43+
this.cliConfigManager = new CliConfigManager(pathResolver);
44+
}
4145

4246
/**
4347
* Find the requested agent if specified, otherwise return the agent if there
@@ -199,7 +203,7 @@ export class Commands {
199203
await this.storage.setSessionToken(res.token);
200204

201205
// Store on disk to be used by the cli.
202-
await this.storage.configureCli(label, url, res.token);
206+
await this.cliConfigManager.configure(label, url, res.token);
203207

204208
// These contexts control various menu items and the sidebar.
205209
await vscode.commands.executeCommand(

src/core/cliConfig.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import fs from "fs/promises";
2+
import path from "path";
3+
import { PathResolver } from "./pathResolver";
4+
5+
export class CliConfigManager {
6+
constructor(private readonly pathResolver: PathResolver) {}
7+
8+
/**
9+
* Configure the CLI for the deployment with the provided label.
10+
*
11+
* Falsey URLs and null tokens are a no-op; we avoid unconfiguring the CLI to
12+
* avoid breaking existing connections.
13+
*/
14+
public async configure(
15+
label: string,
16+
url: string | undefined,
17+
token: string | null,
18+
) {
19+
await Promise.all([
20+
this.updateUrlForCli(label, url),
21+
this.updateTokenForCli(label, token),
22+
]);
23+
}
24+
25+
/**
26+
* Update the URL for the deployment with the provided label on disk which can
27+
* be used by the CLI via --url-file. If the URL is falsey, do nothing.
28+
*
29+
* If the label is empty, read the old deployment-unaware config instead.
30+
*/
31+
private async updateUrlForCli(
32+
label: string,
33+
url: string | undefined,
34+
): Promise<void> {
35+
if (url) {
36+
const urlPath = this.pathResolver.getUrlPath(label);
37+
await fs.mkdir(path.dirname(urlPath), { recursive: true });
38+
await fs.writeFile(urlPath, url);
39+
}
40+
}
41+
42+
/**
43+
* Update the session token for a deployment with the provided label on disk
44+
* which can be used by the CLI via --session-token-file. If the token is
45+
* null, do nothing.
46+
*
47+
* If the label is empty, read the old deployment-unaware config instead.
48+
*/
49+
private async updateTokenForCli(
50+
label: string,
51+
token: string | undefined | null,
52+
) {
53+
if (token !== null) {
54+
const tokenPath = this.pathResolver.getSessionTokenPath(label);
55+
await fs.mkdir(path.dirname(tokenPath), { recursive: true });
56+
await fs.writeFile(tokenPath, token ?? "");
57+
}
58+
}
59+
60+
/**
61+
* Read the CLI config for a deployment with the provided label.
62+
*
63+
* IF a config file does not exist, return an empty string.
64+
*
65+
* If the label is empty, read the old deployment-unaware config.
66+
*/
67+
public async readConfig(
68+
label: string,
69+
): Promise<{ url: string; token: string }> {
70+
const urlPath = this.pathResolver.getUrlPath(label);
71+
const tokenPath = this.pathResolver.getSessionTokenPath(label);
72+
const [url, token] = await Promise.allSettled([
73+
fs.readFile(urlPath, "utf8"),
74+
fs.readFile(tokenPath, "utf8"),
75+
]);
76+
return {
77+
url: url.status === "fulfilled" ? url.value.trim() : "",
78+
token: token.status === "fulfilled" ? token.value.trim() : "",
79+
};
80+
}
81+
}

src/core/pathResolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as path from "path";
2-
import * as vscode from "vscode";
2+
import type { WorkspaceConfiguration } from "vscode";
33

44
export class PathResolver {
55
constructor(
66
private readonly basePath: string,
7-
private readonly configurations: vscode.WorkspaceConfiguration,
7+
private readonly configurations: WorkspaceConfiguration,
88
) {}
99

1010
/**

src/extension.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { errToStr } from "./api/api-helper";
77
import { CoderApi } from "./api/coderApi";
88
import { needToken } from "./api/utils";
99
import { Commands } from "./commands";
10+
import { CliConfigManager } from "./core/cliConfig";
1011
import { PathResolver } from "./core/pathResolver";
1112
import { CertificateError, getErrorDetail } from "./error";
1213
import { Remote } from "./remote";
@@ -107,6 +108,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
107108
allWorkspacesProvider.setVisibility(event.visible);
108109
});
109110

111+
const cliConfigManager = new CliConfigManager(pathResolver);
112+
110113
// Handle vscode:// URIs.
111114
vscode.window.registerUriHandler({
112115
handleUri: async (uri) => {
@@ -160,7 +163,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
160163
}
161164

162165
// Store on disk to be used by the cli.
163-
await storage.configureCli(toSafeHost(url), url, token);
166+
await cliConfigManager.configure(toSafeHost(url), url, token);
164167

165168
vscode.commands.executeCommand(
166169
"coder.open",
@@ -238,7 +241,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
238241
: (params.get("token") ?? "");
239242

240243
// Store on disk to be used by the cli.
241-
await storage.configureCli(toSafeHost(url), url, token);
244+
await cliConfigManager.configure(toSafeHost(url), url, token);
242245

243246
vscode.commands.executeCommand(
244247
"coder.openDevContainer",

src/remote.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { needToken } from "./api/utils";
2121
import { startWorkspaceIfStoppedOrFailed, waitForBuild } from "./api/workspace";
2222
import * as cli from "./cliManager";
2323
import { Commands } from "./commands";
24+
import { CliConfigManager } from "./core/cliConfig";
2425
import { PathResolver } from "./core/pathResolver";
2526
import { featureSetForVersion, FeatureSet } from "./featureSet";
2627
import { getGlobalFlags } from "./globalFlags";
@@ -43,14 +44,17 @@ export interface RemoteDetails extends vscode.Disposable {
4344
}
4445

4546
export class Remote {
47+
private readonly cliConfigManager: CliConfigManager;
4648
public constructor(
4749
// We use the proposed API to get access to useCustom in dialogs.
4850
private readonly vscodeProposed: typeof vscode,
4951
private readonly storage: Storage,
5052
private readonly commands: Commands,
5153
private readonly mode: vscode.ExtensionMode,
5254
private readonly pathResolver: PathResolver,
53-
) {}
55+
) {
56+
this.cliConfigManager = new CliConfigManager(pathResolver);
57+
}
5458

5559
private async confirmStart(workspaceName: string): Promise<boolean> {
5660
const action = await this.vscodeProposed.window.showInformationMessage(
@@ -213,10 +217,10 @@ export class Remote {
213217
const workspaceName = `${parts.username}/${parts.workspace}`;
214218

215219
// Migrate "session_token" file to "session", if needed.
216-
await this.storage.migrateSessionToken(parts.label);
220+
await this.migrateSessionToken(parts.label);
217221

218222
// Get the URL and token belonging to this host.
219-
const { url: baseUrlRaw, token } = await this.storage.readCliConfig(
223+
const { url: baseUrlRaw, token } = await this.cliConfigManager.readConfig(
220224
parts.label,
221225
);
222226

@@ -649,6 +653,22 @@ export class Remote {
649653
};
650654
}
651655

656+
/**
657+
* Migrate the session token file from "session_token" to "session", if needed.
658+
*/
659+
private async migrateSessionToken(label: string) {
660+
const oldTokenPath = this.pathResolver.getLegacySessionTokenPath(label);
661+
const newTokenPath = this.pathResolver.getSessionTokenPath(label);
662+
try {
663+
await fs.rename(oldTokenPath, newTokenPath);
664+
} catch (error) {
665+
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
666+
return;
667+
}
668+
throw error;
669+
}
670+
}
671+
652672
/**
653673
* Return the --log-dir argument value for the ProxyCommand. It may be an
654674
* empty string if the setting is not set or the cli does not support it.

src/storage.ts

Lines changed: 0 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import * as vscode from "vscode";
1313
import { errToStr } from "./api/api-helper";
1414
import * as cli from "./cliManager";
1515
import { PathResolver } from "./core/pathResolver";
16-
import { getHeaderCommand, getHeaders } from "./headers";
1716
import * as pgp from "./pgp";
1817

1918
// Maximium number of recent URLs to store.
@@ -588,107 +587,4 @@ export class Storage {
588587
}
589588
return status;
590589
}
591-
592-
/**
593-
* Configure the CLI for the deployment with the provided label.
594-
*
595-
* Falsey URLs and null tokens are a no-op; we avoid unconfiguring the CLI to
596-
* avoid breaking existing connections.
597-
*/
598-
public async configureCli(
599-
label: string,
600-
url: string | undefined,
601-
token: string | null,
602-
) {
603-
await Promise.all([
604-
this.updateUrlForCli(label, url),
605-
this.updateTokenForCli(label, token),
606-
]);
607-
}
608-
609-
/**
610-
* Update the URL for the deployment with the provided label on disk which can
611-
* be used by the CLI via --url-file. If the URL is falsey, do nothing.
612-
*
613-
* If the label is empty, read the old deployment-unaware config instead.
614-
*/
615-
private async updateUrlForCli(
616-
label: string,
617-
url: string | undefined,
618-
): Promise<void> {
619-
if (url) {
620-
const urlPath = this.pathResolver.getUrlPath(label);
621-
await fs.mkdir(path.dirname(urlPath), { recursive: true });
622-
await fs.writeFile(urlPath, url);
623-
}
624-
}
625-
626-
/**
627-
* Update the session token for a deployment with the provided label on disk
628-
* which can be used by the CLI via --session-token-file. If the token is
629-
* null, do nothing.
630-
*
631-
* If the label is empty, read the old deployment-unaware config instead.
632-
*/
633-
private async updateTokenForCli(
634-
label: string,
635-
token: string | undefined | null,
636-
) {
637-
if (token !== null) {
638-
const tokenPath = this.pathResolver.getSessionTokenPath(label);
639-
await fs.mkdir(path.dirname(tokenPath), { recursive: true });
640-
await fs.writeFile(tokenPath, token ?? "");
641-
}
642-
}
643-
644-
/**
645-
* Read the CLI config for a deployment with the provided label.
646-
*
647-
* IF a config file does not exist, return an empty string.
648-
*
649-
* If the label is empty, read the old deployment-unaware config.
650-
*/
651-
public async readCliConfig(
652-
label: string,
653-
): Promise<{ url: string; token: string }> {
654-
const urlPath = this.pathResolver.getUrlPath(label);
655-
const tokenPath = this.pathResolver.getSessionTokenPath(label);
656-
const [url, token] = await Promise.allSettled([
657-
fs.readFile(urlPath, "utf8"),
658-
fs.readFile(tokenPath, "utf8"),
659-
]);
660-
return {
661-
url: url.status === "fulfilled" ? url.value.trim() : "",
662-
token: token.status === "fulfilled" ? token.value.trim() : "",
663-
};
664-
}
665-
666-
/**
667-
* Migrate the session token file from "session_token" to "session", if needed.
668-
*/
669-
public async migrateSessionToken(label: string) {
670-
const oldTokenPath = this.pathResolver.getLegacySessionTokenPath(label);
671-
const newTokenPath = this.pathResolver.getSessionTokenPath(label);
672-
try {
673-
await fs.rename(oldTokenPath, newTokenPath);
674-
} catch (error) {
675-
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
676-
return;
677-
}
678-
throw error;
679-
}
680-
}
681-
682-
/**
683-
* Run the header command and return the generated headers.
684-
*/
685-
public async getHeaders(
686-
url: string | undefined,
687-
): Promise<Record<string, string>> {
688-
return getHeaders(
689-
url,
690-
getHeaderCommand(vscode.workspace.getConfiguration()),
691-
this.output,
692-
);
693-
}
694590
}

0 commit comments

Comments
 (0)