vmsan 0.1.0-alpha.2 → 0.1.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -21
- package/README.md +91 -47
- package/dist/_chunks/agent.mjs +231 -4
- package/dist/_chunks/connect.mjs +53 -11
- package/dist/_chunks/context.mjs +2380 -0
- package/dist/_chunks/create.mjs +48 -180
- package/dist/_chunks/download.mjs +14 -22
- package/dist/_chunks/errors.mjs +11 -5
- package/dist/_chunks/exec.mjs +190 -0
- package/dist/_chunks/list.mjs +60 -54
- package/dist/_chunks/network.mjs +6 -5
- package/dist/_chunks/remove.mjs +9 -8
- package/dist/_chunks/shell.mjs +2 -0
- package/dist/_chunks/start.mjs +16 -165
- package/dist/_chunks/stop.mjs +8 -7
- package/dist/_chunks/summary.mjs +69 -0
- package/dist/_chunks/timeout-extender.mjs +66 -0
- package/dist/_chunks/timeout-killer.mjs +33 -0
- package/dist/_chunks/upload.mjs +5 -20
- package/dist/_chunks/validation.mjs +1 -1
- package/dist/_chunks/vm-context.mjs +34 -0
- package/dist/_chunks/vm-state.mjs +56 -24
- package/dist/bin/cli.mjs +16 -2
- package/dist/index.d.mts +660 -366
- package/dist/index.mjs +35 -8
- package/package.json +7 -6
- package/dist/_chunks/cleanup.mjs +0 -328
- package/dist/_chunks/connect2.mjs +0 -72
- package/dist/_chunks/environment.mjs +0 -1064
- package/dist/_chunks/image-rootfs.mjs +0 -329
- package/dist/_chunks/vm.mjs +0 -208
package/dist/_chunks/agent.mjs
CHANGED
|
@@ -1,6 +1,196 @@
|
|
|
1
1
|
import { createGzip } from "node:zlib";
|
|
2
2
|
import { Readable } from "node:stream";
|
|
3
3
|
import { pack } from "tar-stream";
|
|
4
|
+
/**
|
|
5
|
+
* Lightweight async iterable queue for pushing events and yielding them to consumers.
|
|
6
|
+
*/
|
|
7
|
+
var AsyncQueue = class {
|
|
8
|
+
_buffer = [];
|
|
9
|
+
_waiting = [];
|
|
10
|
+
_closed = false;
|
|
11
|
+
push(item) {
|
|
12
|
+
if (this._closed) return;
|
|
13
|
+
if (this._waiting.length > 0) this._waiting.shift()({
|
|
14
|
+
value: item,
|
|
15
|
+
done: false
|
|
16
|
+
});
|
|
17
|
+
else this._buffer.push(item);
|
|
18
|
+
}
|
|
19
|
+
close() {
|
|
20
|
+
this._closed = true;
|
|
21
|
+
for (const resolve of this._waiting) resolve({
|
|
22
|
+
value: void 0,
|
|
23
|
+
done: true
|
|
24
|
+
});
|
|
25
|
+
this._waiting.length = 0;
|
|
26
|
+
}
|
|
27
|
+
async *[Symbol.asyncIterator]() {
|
|
28
|
+
while (true) if (this._buffer.length > 0) yield this._buffer.shift();
|
|
29
|
+
else if (this._closed) return;
|
|
30
|
+
else {
|
|
31
|
+
const item = await new Promise((resolve) => {
|
|
32
|
+
this._waiting.push(resolve);
|
|
33
|
+
});
|
|
34
|
+
if (item.done) return;
|
|
35
|
+
yield item.value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var CommandFinished = class {
|
|
40
|
+
cmdId;
|
|
41
|
+
exitCode;
|
|
42
|
+
stdout;
|
|
43
|
+
stderr;
|
|
44
|
+
output;
|
|
45
|
+
timedOut;
|
|
46
|
+
startedAt;
|
|
47
|
+
constructor(opts) {
|
|
48
|
+
this.cmdId = opts.cmdId;
|
|
49
|
+
this.exitCode = opts.exitCode;
|
|
50
|
+
this.stdout = opts.stdout;
|
|
51
|
+
this.stderr = opts.stderr;
|
|
52
|
+
this.output = opts.output;
|
|
53
|
+
this.timedOut = opts.timedOut;
|
|
54
|
+
this.startedAt = opts.startedAt;
|
|
55
|
+
}
|
|
56
|
+
get ok() {
|
|
57
|
+
return this.exitCode === 0;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const MAX_LOG_ENTRIES = 1e5;
|
|
61
|
+
var Command = class {
|
|
62
|
+
cmdId;
|
|
63
|
+
_startedAt;
|
|
64
|
+
_exitCode = null;
|
|
65
|
+
_timedOut = false;
|
|
66
|
+
_logEntries = [];
|
|
67
|
+
_logTruncated = false;
|
|
68
|
+
_eventQueue = new AsyncQueue();
|
|
69
|
+
_completion;
|
|
70
|
+
_stdoutPromise = null;
|
|
71
|
+
_stderrPromise = null;
|
|
72
|
+
_outputPromise = null;
|
|
73
|
+
_agent;
|
|
74
|
+
constructor(init) {
|
|
75
|
+
const { agent, cmdId, startedAt, stream, signal, onStdout, onStderr } = init;
|
|
76
|
+
this._agent = agent;
|
|
77
|
+
this.cmdId = cmdId;
|
|
78
|
+
this._startedAt = startedAt;
|
|
79
|
+
let resolveCompletion;
|
|
80
|
+
let rejectCompletion;
|
|
81
|
+
this._completion = new Promise((resolve, reject) => {
|
|
82
|
+
resolveCompletion = resolve;
|
|
83
|
+
rejectCompletion = reject;
|
|
84
|
+
});
|
|
85
|
+
let onAbort;
|
|
86
|
+
if (signal) {
|
|
87
|
+
onAbort = () => {
|
|
88
|
+
this.kill().catch(() => {});
|
|
89
|
+
};
|
|
90
|
+
if (signal.aborted) this.kill().catch(() => {});
|
|
91
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
92
|
+
}
|
|
93
|
+
(async () => {
|
|
94
|
+
try {
|
|
95
|
+
for await (const event of stream) {
|
|
96
|
+
this._eventQueue.push(event);
|
|
97
|
+
switch (event.type) {
|
|
98
|
+
case "stdout":
|
|
99
|
+
case "stderr":
|
|
100
|
+
if (event.data !== void 0) {
|
|
101
|
+
if (this._logEntries.length < MAX_LOG_ENTRIES) this._logEntries.push({
|
|
102
|
+
stream: event.type,
|
|
103
|
+
data: event.data
|
|
104
|
+
});
|
|
105
|
+
else this._logTruncated = true;
|
|
106
|
+
(event.type === "stdout" ? onStdout : onStderr)?.(event.data);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case "exit":
|
|
110
|
+
this._exitCode = event.exitCode ?? 1;
|
|
111
|
+
break;
|
|
112
|
+
case "timeout":
|
|
113
|
+
this._timedOut = true;
|
|
114
|
+
this._exitCode = 124;
|
|
115
|
+
break;
|
|
116
|
+
case "error":
|
|
117
|
+
this._exitCode = 1;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
this._eventQueue.close();
|
|
122
|
+
resolveCompletion(new CommandFinished({
|
|
123
|
+
cmdId,
|
|
124
|
+
exitCode: this._exitCode ?? 1,
|
|
125
|
+
stdout: this._logEntries.filter((e) => e.stream === "stdout").map((e) => e.data).join("\n"),
|
|
126
|
+
stderr: this._logEntries.filter((e) => e.stream === "stderr").map((e) => e.data).join("\n"),
|
|
127
|
+
output: this._logEntries.map((e) => e.data).join("\n"),
|
|
128
|
+
timedOut: this._timedOut,
|
|
129
|
+
startedAt
|
|
130
|
+
}));
|
|
131
|
+
this._logEntries.length = 0;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
this._eventQueue.close();
|
|
134
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
135
|
+
rejectCompletion(error);
|
|
136
|
+
} finally {
|
|
137
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
138
|
+
}
|
|
139
|
+
})();
|
|
140
|
+
}
|
|
141
|
+
get startedAt() {
|
|
142
|
+
return this._startedAt;
|
|
143
|
+
}
|
|
144
|
+
get exitCode() {
|
|
145
|
+
return this._exitCode;
|
|
146
|
+
}
|
|
147
|
+
async *logs(opts) {
|
|
148
|
+
const signal = opts?.signal;
|
|
149
|
+
if (signal?.aborted) return;
|
|
150
|
+
for await (const event of this._eventQueue) {
|
|
151
|
+
if (signal?.aborted) return;
|
|
152
|
+
if (event.type === "stdout" || event.type === "stderr") yield {
|
|
153
|
+
stream: event.type,
|
|
154
|
+
data: event.data ?? ""
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
stdout(opts) {
|
|
159
|
+
if (!this._stdoutPromise) this._stdoutPromise = this._completion.then((r) => r.stdout);
|
|
160
|
+
return this._withSignal(this._stdoutPromise, opts?.signal);
|
|
161
|
+
}
|
|
162
|
+
stderr(opts) {
|
|
163
|
+
if (!this._stderrPromise) this._stderrPromise = this._completion.then((r) => r.stderr);
|
|
164
|
+
return this._withSignal(this._stderrPromise, opts?.signal);
|
|
165
|
+
}
|
|
166
|
+
output(stream, opts) {
|
|
167
|
+
if (stream === "stdout") return this.stdout(opts);
|
|
168
|
+
if (stream === "stderr") return this.stderr(opts);
|
|
169
|
+
if (!this._outputPromise) this._outputPromise = this._completion.then((r) => r.output);
|
|
170
|
+
return this._withSignal(this._outputPromise, opts?.signal);
|
|
171
|
+
}
|
|
172
|
+
wait(opts) {
|
|
173
|
+
return this._withSignal(this._completion, opts?.signal);
|
|
174
|
+
}
|
|
175
|
+
async kill(signal, opts) {
|
|
176
|
+
await this._agent.killCommand(this.cmdId, signal, opts?.abortSignal);
|
|
177
|
+
}
|
|
178
|
+
_withSignal(promise, signal) {
|
|
179
|
+
if (!signal) return promise;
|
|
180
|
+
if (signal.aborted) return Promise.reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const onAbort = () => reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
183
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
184
|
+
promise.then((value) => {
|
|
185
|
+
signal.removeEventListener("abort", onAbort);
|
|
186
|
+
resolve(value);
|
|
187
|
+
}, (err) => {
|
|
188
|
+
signal.removeEventListener("abort", onAbort);
|
|
189
|
+
reject(err);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
};
|
|
4
194
|
var AgentClient = class {
|
|
5
195
|
constructor(baseUrl, token) {
|
|
6
196
|
this.baseUrl = baseUrl;
|
|
@@ -11,14 +201,15 @@ var AgentClient = class {
|
|
|
11
201
|
if (!res.ok) throw new Error(`Agent health check failed: ${res.status}`);
|
|
12
202
|
return res.json();
|
|
13
203
|
}
|
|
14
|
-
async *run(params) {
|
|
204
|
+
async *run(params, signal) {
|
|
15
205
|
const res = await fetch(`${this.baseUrl}/exec`, {
|
|
16
206
|
method: "POST",
|
|
17
207
|
headers: {
|
|
18
208
|
"Content-Type": "application/json",
|
|
19
209
|
Authorization: `Bearer ${this.token}`
|
|
20
210
|
},
|
|
21
|
-
body: JSON.stringify(params)
|
|
211
|
+
body: JSON.stringify(params),
|
|
212
|
+
signal
|
|
22
213
|
});
|
|
23
214
|
if (!res.ok) {
|
|
24
215
|
const text = await res.text();
|
|
@@ -41,7 +232,7 @@ var AgentClient = class {
|
|
|
41
232
|
}
|
|
42
233
|
if (buffer.trim()) yield JSON.parse(buffer.trim());
|
|
43
234
|
}
|
|
44
|
-
async killCommand(cmdId, signal) {
|
|
235
|
+
async killCommand(cmdId, signal, abortSignal) {
|
|
45
236
|
const url = `${this.baseUrl}/exec/${cmdId}/kill`;
|
|
46
237
|
const res = await fetch(url, {
|
|
47
238
|
method: "POST",
|
|
@@ -49,7 +240,8 @@ var AgentClient = class {
|
|
|
49
240
|
"Content-Type": "application/json",
|
|
50
241
|
Authorization: `Bearer ${this.token}`
|
|
51
242
|
},
|
|
52
|
-
body: signal ? JSON.stringify({ signal }) : void 0
|
|
243
|
+
body: signal ? JSON.stringify({ signal }) : void 0,
|
|
244
|
+
signal: abortSignal
|
|
53
245
|
});
|
|
54
246
|
if (!res.ok) {
|
|
55
247
|
const text = await res.text();
|
|
@@ -94,6 +286,41 @@ var AgentClient = class {
|
|
|
94
286
|
throw new Error(`Agent killShellSession failed (${res.status}): ${text}`);
|
|
95
287
|
}
|
|
96
288
|
}
|
|
289
|
+
async exec(params, opts) {
|
|
290
|
+
const stream = this.run(params, opts?.signal);
|
|
291
|
+
const first = await stream.next();
|
|
292
|
+
if (first.done) throw new Error("Stream ended without 'started' event");
|
|
293
|
+
if (first.value.type !== "started") throw new Error(`Expected 'started' event, got '${first.value.type}'`);
|
|
294
|
+
const cmdId = first.value.id;
|
|
295
|
+
if (!cmdId) throw new Error("'started' event missing 'id' field");
|
|
296
|
+
const startedAt = first.value.ts ? new Date(first.value.ts) : /* @__PURE__ */ new Date();
|
|
297
|
+
return new Command({
|
|
298
|
+
agent: this,
|
|
299
|
+
cmdId,
|
|
300
|
+
startedAt,
|
|
301
|
+
stream,
|
|
302
|
+
signal: opts?.signal,
|
|
303
|
+
onStdout: opts?.onStdout,
|
|
304
|
+
onStderr: opts?.onStderr
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
async runCommand(cmdOrParams, args, opts) {
|
|
308
|
+
let params;
|
|
309
|
+
if (typeof cmdOrParams === "string") params = {
|
|
310
|
+
cmd: cmdOrParams,
|
|
311
|
+
args,
|
|
312
|
+
signal: opts?.signal
|
|
313
|
+
};
|
|
314
|
+
else params = cmdOrParams;
|
|
315
|
+
const { signal, onStdout, onStderr, ...runParams } = params;
|
|
316
|
+
const command = await this.exec(runParams, {
|
|
317
|
+
signal,
|
|
318
|
+
onStdout,
|
|
319
|
+
onStderr
|
|
320
|
+
});
|
|
321
|
+
if (params.detached) return command;
|
|
322
|
+
return command.wait({ signal });
|
|
323
|
+
}
|
|
97
324
|
async readFile(path) {
|
|
98
325
|
const res = await fetch(`${this.baseUrl}/files/read`, {
|
|
99
326
|
method: "POST",
|
package/dist/_chunks/connect.mjs
CHANGED
|
@@ -1,13 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { n as vmsanPaths } from "./paths.mjs";
|
|
2
|
+
import { t as handleCommandError } from "./errors.mjs";
|
|
3
|
+
import { t as createCommandLogger } from "./logger.mjs";
|
|
4
|
+
import "./vm-state.mjs";
|
|
5
|
+
import { n as waitForAgent, t as resolveVmState } from "./vm-context.mjs";
|
|
6
|
+
import { t as ShellSession } from "./shell.mjs";
|
|
7
|
+
import { consola } from "consola";
|
|
8
|
+
import { defineCommand } from "citty";
|
|
9
|
+
const connectCommand = defineCommand({
|
|
10
|
+
meta: {
|
|
11
|
+
name: "connect",
|
|
12
|
+
description: "Connect to a running VM"
|
|
13
|
+
},
|
|
14
|
+
args: {
|
|
15
|
+
vmId: {
|
|
16
|
+
type: "positional",
|
|
17
|
+
description: "VM ID to connect to",
|
|
18
|
+
required: true
|
|
19
|
+
},
|
|
20
|
+
session: {
|
|
21
|
+
type: "string",
|
|
22
|
+
alias: "s",
|
|
23
|
+
description: "Attach to an existing shell session ID",
|
|
24
|
+
required: false
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
async run({ args }) {
|
|
28
|
+
const cmdLog = createCommandLogger("connect");
|
|
29
|
+
const paths = vmsanPaths();
|
|
6
30
|
try {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
31
|
+
const { state, guestIp, port } = resolveVmState(args.vmId, paths);
|
|
32
|
+
consola.debug(`Agent endpoint: ${guestIp}:${port}`);
|
|
33
|
+
consola.debug("Waiting for agent to become ready...");
|
|
34
|
+
await waitForAgent(guestIp, port);
|
|
35
|
+
const shell = new ShellSession({
|
|
36
|
+
host: guestIp,
|
|
37
|
+
port,
|
|
38
|
+
token: state.agentToken,
|
|
39
|
+
sessionId: args.session
|
|
40
|
+
});
|
|
41
|
+
const closeInfo = await shell.connect();
|
|
42
|
+
cmdLog.set({
|
|
43
|
+
vmId: args.vmId,
|
|
44
|
+
method: "pty"
|
|
45
|
+
});
|
|
46
|
+
cmdLog.emit();
|
|
47
|
+
if (!closeInfo.sessionDestroyed && shell.sessionId) process.stderr.write(`\n[2mResume this session with:\n vmsan connect ${args.vmId} --session ${shell.sessionId}[0m\n`);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
handleCommandError(error, cmdLog);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
10
53
|
}
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
export { waitForAgent as t };
|
|
54
|
+
});
|
|
55
|
+
export { connectCommand as default };
|