-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathlocalstack-management.ts
More file actions
206 lines (179 loc) · 7.19 KB
/
localstack-management.ts
File metadata and controls
206 lines (179 loc) · 7.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import { z } from "zod";
import { type ToolMetadata, type InferSchema } from "xmcp";
import { spawn } from "child_process";
import { getLocalStackStatus } from "../lib/localstack/localstack.utils";
import { runCommand } from "../core/command-runner";
import { runPreflights, requireLocalStackCli } from "../core/preflight";
import { ResponseBuilder } from "../core/response-builder";
export const schema = {
action: z
.enum(["start", "stop", "restart", "status"])
.describe("The LocalStack management action to perform"),
envVars: z
.record(z.string())
.optional()
.describe("Additional environment variables as key-value pairs (only for start action)"),
};
export const metadata: ToolMetadata = {
name: "localstack-management",
description: "Manage LocalStack lifecycle: start, stop, restart, or check status",
annotations: {
title: "LocalStack Management",
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
},
};
export default async function localstackManagement({
action,
envVars,
}: InferSchema<typeof schema>) {
const preflightError = await runPreflights([requireLocalStackCli()]);
if (preflightError) return preflightError;
switch (action) {
case "start":
return await handleStart({ envVars });
case "stop":
return await handleStop();
case "restart":
return await handleRestart();
case "status":
return await handleStatus();
default:
return ResponseBuilder.error(
"Unknown action",
`❌ Unknown action: ${action}. Supported actions: start, stop, restart, status`
);
}
}
// Handle start action
async function handleStart({ envVars }: { envVars?: Record<string, string> }) {
const statusCheck = await getLocalStackStatus();
if (statusCheck.isRunning) {
return ResponseBuilder.markdown(
"⚠️ LocalStack is already running. Use 'restart' if you want to apply new configuration."
);
}
const environment = { ...process.env, ...(envVars || {}) } as Record<string, string>;
if (process.env.LOCALSTACK_AUTH_TOKEN) {
environment.LOCALSTACK_AUTH_TOKEN = process.env.LOCALSTACK_AUTH_TOKEN;
}
return new Promise((resolve) => {
const child = spawn("localstack", ["start"], {
env: environment,
stdio: ["ignore", "ignore", "pipe"],
});
let stderr = "";
child.stderr.on("data", (data) => {
stderr += data.toString();
});
let earlyExit = false;
let poll: NodeJS.Timeout;
child.on("error", (err) => {
earlyExit = true;
if (poll) clearInterval(poll);
resolve(ResponseBuilder.markdown(`❌ Failed to start LocalStack process: ${err.message}`));
});
child.on("close", (code) => {
if (earlyExit) return;
if (poll) clearInterval(poll);
if (code !== 0) {
resolve(
ResponseBuilder.markdown(
`❌ LocalStack process exited unexpectedly with code ${code}.\n\nStderr:\n${stderr}`
)
);
}
});
const pollInterval = 5000;
const maxWaitTime = 120000;
let timeWaited = 0;
poll = setInterval(async () => {
timeWaited += pollInterval;
const status = await getLocalStackStatus();
if (status.isReady || status.isRunning) {
clearInterval(poll);
let resultMessage = "🚀 LocalStack started successfully!\n\n";
if (envVars)
resultMessage += `✅ Custom environment variables applied: ${Object.keys(envVars).join(", ")}\n`;
resultMessage += `\n**Status:**\n${status.statusOutput}`;
resolve(ResponseBuilder.markdown(resultMessage));
} else if (timeWaited >= maxWaitTime) {
clearInterval(poll);
resolve(
ResponseBuilder.markdown(
`❌ LocalStack start timed out after ${maxWaitTime / 1000} seconds. It may still be starting in the background.`
)
);
}
}, pollInterval);
});
}
// Handle stop action
async function handleStop() {
const cmd = await runCommand("localstack", ["stop"]);
let result = "🛑 LocalStack stop command executed successfully!\n";
if (cmd.stdout.trim()) result += `\nOutput:\n${cmd.stdout}`;
if (cmd.stderr.trim()) result += `\nMessages:\n${cmd.stderr}`;
const statusResult = await getLocalStackStatus();
if (!statusResult.isRunning) {
result += "\n\n✅ LocalStack has been stopped successfully.";
} else if (statusResult.errorMessage) {
result += "\n\n✅ LocalStack appears to be stopped.";
} else {
result += "\n\n⚠️ LocalStack may still be running. Check the status manually if needed.";
}
if (cmd.error) {
result = `❌ Failed to stop LocalStack: ${cmd.error.message}\n\nThis could happen if:\n- LocalStack is not currently running\n- There was an error executing the stop command\n- Permission issues\n\nYou can try checking the LocalStack status first to see if it's running.`;
}
return ResponseBuilder.markdown(result);
}
// Handle restart action
async function handleRestart() {
const cmd = await runCommand("localstack", ["restart"], { timeout: 30000 });
let result = "🔄 LocalStack restart command executed!\n\n";
if (cmd.stdout.trim()) result += `Output:\n${cmd.stdout}\n`;
if (cmd.stderr.trim()) result += `Messages:\n${cmd.stderr}\n`;
await new Promise((resolve) => setTimeout(resolve, 2000));
const statusResult = await getLocalStackStatus();
if (statusResult.statusOutput) {
result += `\nStatus after restart:\n${statusResult.statusOutput}`;
if (statusResult.isRunning) {
result +=
"\n\n✅ LocalStack has been restarted successfully and is now running with a fresh state.";
} else {
result +=
"\n\n⚠️ LocalStack restart completed but may still be starting up. Check status again in a few moments.";
}
} else {
result +=
"\n\n⚠️ Restart completed but unable to verify status. LocalStack may still be starting up.";
}
if (cmd.error) {
result = `❌ Failed to restart LocalStack: ${cmd.error.message}\n\nThis could happen if:\n- LocalStack is not currently installed properly\n- There was an error executing the restart command\n- The restart process timed out (LocalStack can take time to restart)\n- Permission issues\n\nYou can try stopping and starting LocalStack manually using separate actions if the restart action continues to fail.`;
}
return ResponseBuilder.markdown(result);
}
// Handle status action
async function handleStatus() {
const statusResult = await getLocalStackStatus();
if (statusResult.statusOutput) {
let result = "📊 LocalStack Status:\n\n";
result += statusResult.statusOutput;
// Add helpful information based on the status
if (statusResult.isRunning) {
result += "\n\n✅ LocalStack is currently running and ready to accept requests.";
} else {
result += "\n\n⚠️ LocalStack is not currently running. Use the start action to start it.";
}
return ResponseBuilder.markdown(result);
} else {
const result = `❌ ${statusResult.errorMessage}
This could happen if:
- LocalStack is not installed properly
- There was an error executing the status command
- LocalStack services are not accessible
Try running the CLI check first to verify your LocalStack installation.`;
return ResponseBuilder.markdown(result);
}
}