tangled
alpha
login
or
join now
tsiry-sandratraina.com
/
vmx
1
fork
atom
A Docker-like CLI and HTTP API for managing headless VMs
1
fork
atom
overview
issues
pulls
pipelines
fix volume issue in machine API
tsiry-sandratraina.com
3 months ago
8307d8e8
1fc72372
+80
-64
1 changed file
expand all
collapse all
unified
split
src
api
machines.ts
+80
-64
src/api/machines.ts
···
1
1
-
import { Hono } from "hono";
1
1
+
import { createId } from "@paralleldrive/cuid2";
2
2
import { Data, Effect, pipe } from "effect";
3
3
-
import {
4
4
-
createVolumeIfNeeded,
5
5
-
handleError,
6
6
-
parseCreateMachineRequest,
7
7
-
parseParams,
8
8
-
parseQueryParams,
9
9
-
parseStartRequest,
10
10
-
presentation,
11
11
-
} from "./utils.ts";
3
3
+
import { Hono } from "hono";
4
4
+
import Moniker from "moniker";
5
5
+
import { getImage } from "../images.ts";
12
6
import { DEFAULT_VERSION, getInstanceState } from "../mod.ts";
7
7
+
import { generateRandomMacAddress } from "../network.ts";
13
8
import {
14
9
listInstances,
15
10
removeInstanceState,
16
11
saveInstanceState,
17
12
} from "../state.ts";
18
18
-
import { findVm, killProcess, updateToStopped } from "../subcommands/stop.ts";
19
13
import {
20
14
buildQemuArgs,
21
15
createLogsDir,
···
23
17
setupFirmware,
24
18
startDetachedQemu,
25
19
} from "../subcommands/start.ts";
20
20
+
import { findVm, killProcess, updateToStopped } from "../subcommands/stop.ts";
26
21
import type { NewMachine } from "../types.ts";
27
27
-
import { createId } from "@paralleldrive/cuid2";
28
28
-
import { generateRandomMacAddress } from "../network.ts";
29
29
-
import Moniker from "moniker";
30
30
-
import { getImage } from "../images.ts";
22
22
+
import { getVolume } from "../volumes.ts";
23
23
+
import {
24
24
+
createVolumeIfNeeded,
25
25
+
handleError,
26
26
+
parseCreateMachineRequest,
27
27
+
parseParams,
28
28
+
parseQueryParams,
29
29
+
parseStartRequest,
30
30
+
presentation,
31
31
+
} from "./utils.ts";
31
32
32
33
export class ImageNotFoundError extends Data.TaggedError("ImageNotFoundError")<{
33
34
id: string;
34
35
}> {}
35
36
36
37
export class RemoveRunningVmError extends Data.TaggedError(
37
37
-
"RemoveRunningVmError",
38
38
+
"RemoveRunningVmError"
38
39
)<{
39
40
id: string;
40
41
}> {}
···
46
47
pipe(
47
48
parseQueryParams(c),
48
49
Effect.flatMap((params) =>
49
49
-
listInstances(
50
50
-
params.all === "true" || params.all === "1",
51
51
-
)
50
50
+
listInstances(params.all === "true" || params.all === "1")
52
51
),
53
53
-
presentation(c),
54
54
-
),
55
55
-
));
52
52
+
presentation(c)
53
53
+
)
54
54
+
)
55
55
+
);
56
56
57
57
app.post("/", (c) =>
58
58
Effect.runPromise(
···
63
63
const image = yield* getImage(params.image);
64
64
if (!image) {
65
65
return yield* Effect.fail(
66
66
-
new ImageNotFoundError({ id: params.image }),
66
66
+
new ImageNotFoundError({ id: params.image })
67
67
);
68
68
}
69
69
···
97
97
})
98
98
),
99
99
presentation(c),
100
100
-
Effect.catchAll((error) => handleError(error, c)),
101
101
-
),
102
102
-
));
100
100
+
Effect.catchAll((error) => handleError(error, c))
101
101
+
)
102
102
+
)
103
103
+
);
103
104
104
105
app.get("/:id", (c) =>
105
106
Effect.runPromise(
106
107
pipe(
107
108
parseParams(c),
108
109
Effect.flatMap(({ id }) => getInstanceState(id)),
109
109
-
presentation(c),
110
110
-
),
111
111
-
));
110
110
+
presentation(c)
111
111
+
)
112
112
+
)
113
113
+
);
112
114
113
115
app.delete("/:id", (c) =>
114
116
Effect.runPromise(
···
127
129
})
128
130
),
129
131
presentation(c),
130
130
-
Effect.catchAll((error) => handleError(error, c)),
131
131
-
),
132
132
-
));
132
132
+
Effect.catchAll((error) => handleError(error, c))
133
133
+
)
134
134
+
)
135
135
+
);
133
136
134
137
app.post("/:id/start", (c) =>
135
138
Effect.runPromise(
136
139
pipe(
137
140
Effect.all([parseParams(c), parseStartRequest(c)]),
138
138
-
Effect.flatMap((
139
139
-
[{ id }, startRequest],
140
140
-
) => Effect.all([findVm(id), Effect.succeed(startRequest)])),
141
141
+
Effect.flatMap(([{ id }, startRequest]) =>
142
142
+
Effect.all([findVm(id), Effect.succeed(startRequest)])
143
143
+
),
141
144
Effect.flatMap(([vm, startRequest]) =>
142
145
Effect.gen(function* () {
143
146
yield* failIfVMRunning(vm);
147
147
+
const volume = yield* getVolume(vm.drivePath || "");
144
148
const firmwareArgs = yield* setupFirmware();
145
145
-
const qemuArgs = yield* buildQemuArgs({
146
146
-
...vm,
147
147
-
cpu: String(startRequest.cpu ?? vm.cpu),
148
148
-
cpus: startRequest.cpus ?? vm.cpus,
149
149
-
memory: startRequest.memory ?? vm.memory,
150
150
-
portForward: startRequest.portForward
151
151
-
? startRequest.portForward.join(",")
152
152
-
: vm.portForward,
153
153
-
}, firmwareArgs);
149
149
+
vm.volume = volume ? volume.path : undefined;
150
150
+
const qemuArgs = yield* buildQemuArgs(
151
151
+
{
152
152
+
...vm,
153
153
+
cpu: String(startRequest.cpu ?? vm.cpu),
154
154
+
cpus: startRequest.cpus ?? vm.cpus,
155
155
+
memory: startRequest.memory ?? vm.memory,
156
156
+
portForward: startRequest.portForward
157
157
+
? startRequest.portForward.join(",")
158
158
+
: vm.portForward,
159
159
+
},
160
160
+
firmwareArgs
161
161
+
);
154
162
yield* createLogsDir();
155
163
yield* startDetachedQemu(vm.id, vm, qemuArgs);
156
164
return { ...vm, status: "RUNNING" };
157
165
})
158
166
),
159
167
presentation(c),
160
160
-
Effect.catchAll((error) => handleError(error, c)),
161
161
-
),
162
162
-
));
168
168
+
Effect.catchAll((error) => handleError(error, c))
169
169
+
)
170
170
+
)
171
171
+
);
163
172
164
173
app.post("/:id/stop", (c) =>
165
174
Effect.runPromise(
···
169
178
Effect.flatMap(killProcess),
170
179
Effect.flatMap(updateToStopped),
171
180
presentation(c),
172
172
-
Effect.catchAll((error) => handleError(error, c)),
173
173
-
),
174
174
-
));
181
181
+
Effect.catchAll((error) => handleError(error, c))
182
182
+
)
183
183
+
)
184
184
+
);
175
185
176
186
app.post("/:id/restart", (c) =>
177
187
Effect.runPromise(
···
181
191
Effect.flatMap(killProcess),
182
192
Effect.flatMap(updateToStopped),
183
193
Effect.flatMap(() => Effect.all([parseParams(c), parseStartRequest(c)])),
184
184
-
Effect.flatMap((
185
185
-
[{ id }, startRequest],
186
186
-
) => Effect.all([findVm(id), Effect.succeed(startRequest)])),
194
194
+
Effect.flatMap(([{ id }, startRequest]) =>
195
195
+
Effect.all([findVm(id), Effect.succeed(startRequest)])
196
196
+
),
187
197
Effect.flatMap(([vm, startRequest]) =>
188
198
Effect.gen(function* () {
189
199
const firmwareArgs = yield* setupFirmware();
190
190
-
const qemuArgs = yield* buildQemuArgs({
191
191
-
...vm,
192
192
-
cpu: String(startRequest.cpus ?? vm.cpu),
193
193
-
memory: startRequest.memory ?? vm.memory,
194
194
-
portForward: startRequest.portForward
195
195
-
? startRequest.portForward.join(",")
196
196
-
: vm.portForward,
197
197
-
}, firmwareArgs);
200
200
+
const volume = yield* getVolume(vm.drivePath || "");
201
201
+
vm.volume = volume ? volume.path : undefined;
202
202
+
const qemuArgs = yield* buildQemuArgs(
203
203
+
{
204
204
+
...vm,
205
205
+
cpu: String(startRequest.cpus ?? vm.cpu),
206
206
+
memory: startRequest.memory ?? vm.memory,
207
207
+
portForward: startRequest.portForward
208
208
+
? startRequest.portForward.join(",")
209
209
+
: vm.portForward,
210
210
+
},
211
211
+
firmwareArgs
212
212
+
);
198
213
yield* createLogsDir();
199
214
yield* startDetachedQemu(vm.id, vm, qemuArgs);
200
215
return { ...vm, status: "RUNNING" };
201
216
})
202
217
),
203
218
presentation(c),
204
204
-
Effect.catchAll((error) => handleError(error, c)),
205
205
-
),
206
206
-
));
219
219
+
Effect.catchAll((error) => handleError(error, c))
220
220
+
)
221
221
+
)
222
222
+
);
207
223
208
224
export default app;