Skip to content

Commit aa0520a

Browse files
skyrpextiurinanisaoshafi
authored
feat: improve cli detection (#60)
This change refactors the CLI detection in favor of an event-based status tracker. This improves performance as we don't require periodic checks anymore. In addition, it detects existing outdated LocalStack CLI versions (versions <= 4) and offers to run the setup wizard. Finally, some rudimentary tests for the LocalStack Instance status tracker were added. --------- Co-authored-by: Misha Tiurin <[email protected]> Co-authored-by: Anisa Oshafi <[email protected]> Co-authored-by: Misha Tiurin <[email protected]>
1 parent 3e3924d commit aa0520a

25 files changed

+1086
-720
lines changed

package.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,6 @@
5555
"title": "Run Setup Wizard",
5656
"category": "LocalStack"
5757
},
58-
{
59-
"command": "localstack.refreshStatusBar",
60-
"title": "Refresh Status Bar",
61-
"category": "LocalStack",
62-
"enablement": "false"
63-
},
6458
{
6559
"command": "localstack.showCommands",
6660
"title": "Show Commands",

src/constants.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,26 @@ export const GLOBAL_CLI_INSTALLATION_DIRNAME = join(
1515
);
1616

1717
const CLI_UNIX_PATHS = [
18+
// The local installation path takes precedence.
19+
join(LOCAL_CLI_INSTALLATION_DIRNAME, "localstack"),
20+
// Check if it's in the PATH.
1821
"localstack",
22+
// Common installation paths.
1923
join("/", "usr", "bin", "localstack"),
2024
join("/", "usr", "local", "bin", "localstack"),
2125
join("/", "opt", "homebrew", "bin", "localstack"),
2226
join("/", "home", "linuxbrew", ".linuxbrew", "bin", "localstack"),
2327
join(homedir(), ".linuxbrew", "bin", "localstack"),
2428
join(homedir(), ".local", "bin", "localstack"),
25-
join(LOCAL_CLI_INSTALLATION_DIRNAME, "localstack"),
2629
];
2730

2831
const CLI_WINDOWS_PATHS = [
32+
// The local installation path takes precedence.
33+
join(LOCAL_CLI_INSTALLATION_DIRNAME, "localstack.exe"),
34+
// Check if it's in the PATH.
2935
"localstack.exe",
36+
// Common installation paths.
3037
join(GLOBAL_CLI_INSTALLATION_DIRNAME, "localstack", "localstack.exe"),
31-
join(LOCAL_CLI_INSTALLATION_DIRNAME, "localstack.exe"),
3238
];
3339

3440
export const CLI_PATHS =

src/extension.ts

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ import manage from "./plugins/manage.ts";
88
import setup from "./plugins/setup.ts";
99
import statusBar from "./plugins/status-bar.ts";
1010
import { PluginManager } from "./plugins.ts";
11-
import { createContainerStatusTracker } from "./utils/container-status.ts";
12-
import { createLocalStackStatusTracker } from "./utils/localstack-status.ts";
11+
import { createCliStatusTracker } from "./utils/cli.ts";
12+
import { createLocalStackContainerStatusTracker } from "./utils/localstack-container.ts";
13+
import {
14+
createHealthStatusTracker,
15+
createLocalStackInstanceStatusTracker,
16+
} from "./utils/localstack-instance.ts";
1317
import { getOrCreateExtensionSessionId } from "./utils/manage.ts";
14-
import { createSetupStatusTracker } from "./utils/setup-status.ts";
18+
import { createSetupStatusTracker } from "./utils/setup.ts";
1519
import { createTelemetry } from "./utils/telemetry.ts";
1620
import { createTimeTracker } from "./utils/time-tracker.ts";
1721

@@ -29,6 +33,9 @@ export async function activate(context: ExtensionContext) {
2933
});
3034
context.subscriptions.push(outputChannel);
3135

36+
const cliStatusTracker = createCliStatusTracker(outputChannel);
37+
context.subscriptions.push(cliStatusTracker);
38+
3239
const timeTracker = createTimeTracker({ outputChannel });
3340

3441
const {
@@ -46,45 +53,37 @@ export async function activate(context: ExtensionContext) {
4653
statusBarItem.text = "$(loading~spin) LocalStack";
4754
statusBarItem.show();
4855

49-
const containerStatusTracker = await createContainerStatusTracker(
56+
const containerStatusTracker = createLocalStackContainerStatusTracker(
5057
"localstack-main",
5158
outputChannel,
5259
timeTracker,
5360
);
5461
context.subscriptions.push(containerStatusTracker);
5562

56-
const localStackStatusTracker = createLocalStackStatusTracker(
63+
const healthCheckStatusTracker = createHealthStatusTracker(timeTracker);
64+
const localStackStatusTracker = createLocalStackInstanceStatusTracker(
5765
containerStatusTracker,
66+
healthCheckStatusTracker,
5867
outputChannel,
59-
timeTracker,
6068
);
6169
context.subscriptions.push(localStackStatusTracker);
6270

63-
outputChannel.trace(`[setup-status]: Starting...`);
64-
const startStatusTracker = Date.now();
65-
const setupStatusTracker = await createSetupStatusTracker(
66-
outputChannel,
67-
timeTracker,
71+
const setupStatusTracker = await timeTracker.run(
72+
"setup-status",
73+
async () => {
74+
return await createSetupStatusTracker(
75+
outputChannel,
76+
timeTracker,
77+
cliStatusTracker,
78+
);
79+
},
6880
);
6981
context.subscriptions.push(setupStatusTracker);
70-
const endStatusTracker = Date.now();
71-
outputChannel.trace(
72-
`[setup-status]: Completed in ${ms(
73-
endStatusTracker - startStatusTracker,
74-
{ long: true },
75-
)}`,
76-
);
7782

78-
const startTelemetry = Date.now();
79-
outputChannel.trace(`[telemetry]: Starting...`);
80-
const sessionId = await getOrCreateExtensionSessionId(context);
81-
const telemetry = createTelemetry(outputChannel, sessionId);
82-
const endTelemetry = Date.now();
83-
outputChannel.trace(
84-
`[telemetry]: Completed in ${ms(endTelemetry - startTelemetry, {
85-
long: true,
86-
})}`,
87-
);
83+
const telemetry = await timeTracker.run("telemetry", async () => {
84+
const sessionId = await getOrCreateExtensionSessionId(context);
85+
return createTelemetry(outputChannel, sessionId);
86+
});
8887

8988
return {
9089
statusBarItem,
@@ -100,6 +99,7 @@ export async function activate(context: ExtensionContext) {
10099
context,
101100
outputChannel,
102101
statusBarItem,
102+
cliStatusTracker,
103103
containerStatusTracker,
104104
localStackStatusTracker,
105105
setupStatusTracker,

src/plugins.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import ms from "ms";
22
import type { ExtensionContext, LogOutputChannel, StatusBarItem } from "vscode";
33

4-
import type { ContainerStatusTracker } from "./utils/container-status.ts";
5-
import type { LocalStackStatusTracker } from "./utils/localstack-status.ts";
6-
import type { SetupStatusTracker } from "./utils/setup-status.ts";
4+
import type { CliStatusTracker } from "./utils/cli.ts";
5+
import type { LocalStackContainerStatusTracker } from "./utils/localstack-container.ts";
6+
import type { LocalStackInstanceStatusTracker } from "./utils/localstack-instance.ts";
7+
import type { SetupStatusTracker } from "./utils/setup.ts";
78
import type { Telemetry } from "./utils/telemetry.ts";
89
import type { TimeTracker } from "./utils/time-tracker.ts";
910

@@ -13,8 +14,9 @@ export interface PluginOptions {
1314
context: ExtensionContext;
1415
outputChannel: LogOutputChannel;
1516
statusBarItem: StatusBarItem;
16-
containerStatusTracker: ContainerStatusTracker;
17-
localStackStatusTracker: LocalStackStatusTracker;
17+
cliStatusTracker: CliStatusTracker;
18+
containerStatusTracker: LocalStackContainerStatusTracker;
19+
localStackStatusTracker: LocalStackInstanceStatusTracker;
1820
setupStatusTracker: SetupStatusTracker;
1921
telemetry: Telemetry;
2022
timeTracker: TimeTracker;

src/plugins/manage.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,30 @@ import {
99

1010
export default createPlugin(
1111
"manage",
12-
({ context, outputChannel, telemetry, localStackStatusTracker }) => {
12+
({
13+
context,
14+
outputChannel,
15+
telemetry,
16+
localStackStatusTracker,
17+
cliStatusTracker,
18+
}) => {
1319
context.subscriptions.push(
1420
commands.registerCommand("localstack.start", async () => {
21+
const cliPath = cliStatusTracker.cliPath();
22+
if (!cliPath) {
23+
void window.showInformationMessage(
24+
"LocalStack CLI could not be found. Please, run the setup wizard.",
25+
);
26+
return;
27+
}
28+
1529
if (localStackStatusTracker.status() !== "stopped") {
1630
window.showInformationMessage("LocalStack is already running.");
1731
return;
1832
}
1933
localStackStatusTracker.forceContainerStatus("running");
2034
try {
21-
await startLocalStack(outputChannel, telemetry);
35+
await startLocalStack(cliPath, outputChannel, telemetry);
2236
} catch {
2337
localStackStatusTracker.forceContainerStatus("stopped");
2438
}
@@ -27,12 +41,20 @@ export default createPlugin(
2741

2842
context.subscriptions.push(
2943
commands.registerCommand("localstack.stop", () => {
44+
const cliPath = cliStatusTracker.cliPath();
45+
if (!cliPath) {
46+
void window.showInformationMessage(
47+
"LocalStack CLI could not be found. Please, run the setup wizard.",
48+
);
49+
return;
50+
}
51+
3052
if (localStackStatusTracker.status() !== "running") {
3153
window.showInformationMessage("LocalStack is not running.");
3254
return;
3355
}
3456
localStackStatusTracker.forceContainerStatus("stopping");
35-
void stopLocalStack(outputChannel, telemetry);
57+
void stopLocalStack(cliPath, outputChannel, telemetry);
3658
}),
3759
);
3860

src/plugins/setup.ts

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,34 @@ import {
77
saveAuthToken,
88
readAuthToken,
99
} from "../utils/authenticate.ts";
10+
import { findLocalStack } from "../utils/cli.ts";
1011
import { configureAwsProfiles } from "../utils/configure-aws.ts";
1112
import { runInstallProcess } from "../utils/install.ts";
1213
import {
1314
activateLicense,
1415
checkIsLicenseValid,
1516
activateLicenseUntilValid,
1617
} from "../utils/license.ts";
17-
import { minDelay } from "../utils/promises.ts";
18+
import { minDelay } from "../utils/min-delay.ts";
1819
import { updateDockerImage } from "../utils/setup.ts";
1920
import { get_setup_ended } from "../utils/telemetry.ts";
2021

22+
async function getValidCliPath() {
23+
const cli = await findLocalStack();
24+
if (!cli.cliPath || !cli.executable || !cli.found || !cli.upToDate) {
25+
return;
26+
}
27+
return cli.cliPath;
28+
}
29+
2130
export default createPlugin(
2231
"setup",
2332
({
2433
context,
2534
outputChannel,
2635
setupStatusTracker,
2736
localStackStatusTracker,
37+
cliStatusTracker,
2838
telemetry,
2939
}) => {
3040
context.subscriptions.push(
@@ -43,7 +53,7 @@ export default createPlugin(
4353
},
4454
});
4555

46-
window.withProgress(
56+
void window.withProgress(
4757
{
4858
location: ProgressLocation.Notification,
4959
title: "Setup LocalStack",
@@ -55,13 +65,14 @@ export default createPlugin(
5565
let authenticationStatus: "COMPLETED" | "SKIPPED" = "COMPLETED";
5666
{
5767
const installationStartedAt = new Date().toISOString();
58-
const { cancelled, skipped } = await runInstallProcess(
68+
const { cancelled, skipped } = await runInstallProcess({
69+
cliPath: cliStatusTracker.cliPath(),
5970
progress,
6071
cancellationToken,
6172
outputChannel,
6273
telemetry,
63-
origin_trigger,
64-
);
74+
origin: origin_trigger,
75+
});
6576
cliStatus = skipped === true ? "SKIPPED" : "COMPLETED";
6677
if (cancelled || cancellationToken.isCancellationRequested) {
6778
telemetry.track({
@@ -221,14 +232,52 @@ export default createPlugin(
221232
/////////////////////////////////////////////////////////////////////
222233
progress.report({ message: "Checking LocalStack license..." });
223234

235+
// If the CLI status tracker doesn't have a valid CLI path yet,
236+
// we must find it manually. This may occur when installing the
237+
// CLI as part of the setup process: the CLI status tracker will
238+
// detect the CLI path the next tick.
239+
const cliPath =
240+
cliStatusTracker.cliPath() ?? (await getValidCliPath());
241+
if (!cliPath) {
242+
telemetry.track(
243+
get_setup_ended(
244+
cliStatus,
245+
authenticationStatus,
246+
"CANCELLED",
247+
"CANCELLED",
248+
"FAILED",
249+
origin_trigger,
250+
await readAuthToken(),
251+
),
252+
);
253+
void window
254+
.showErrorMessage(
255+
"Could not access the LocalStack CLI.",
256+
{
257+
title: "Restart Setup",
258+
command: "localstack.setup",
259+
},
260+
{
261+
title: "View Logs",
262+
command: "localstack.viewLogs",
263+
},
264+
)
265+
.then((selection) => {
266+
if (selection) {
267+
void commands.executeCommand(selection.command);
268+
}
269+
});
270+
return;
271+
}
272+
224273
// If an auth token has just been obtained or LocalStack has never been started,
225274
// then there will be no license info to be reported by `localstack license info`.
226275
// Also, an expired license could be cached.
227276
// Activating the license pre-emptively to know its state during the setup process.
228277
const licenseCheckStartedAt = new Date().toISOString();
229278
const licenseIsValid = await minDelay(
230-
activateLicense(outputChannel).then(() =>
231-
checkIsLicenseValid(outputChannel),
279+
activateLicense(cliPath, outputChannel).then(() =>
280+
checkIsLicenseValid(cliPath, outputChannel),
232281
),
233282
);
234283
if (!licenseIsValid) {
@@ -240,6 +289,7 @@ export default createPlugin(
240289
await commands.executeCommand("localstack.openLicensePage");
241290

242291
await activateLicenseUntilValid(
292+
cliPath,
243293
outputChannel,
244294
cancellationToken,
245295
);

0 commit comments

Comments
 (0)