A Docker-like CLI and HTTP API for managing headless VMs
at main 89 lines 2.4 kB view raw
1import { createId } from "@paralleldrive/cuid2"; 2import { Effect, pipe } from "effect"; 3import { Hono } from "hono"; 4import type { VirtualMachine } from "../db.ts"; 5import { ImageNotFoundError, VmNotFoundError } from "../errors.ts"; 6import { deleteImage, getImage, listImages, saveImage } from "../images.ts"; 7import { getInstanceState } from "../state.ts"; 8import { du, extractTag } from "../utils.ts"; 9import { 10 handleError, 11 parseCreateImageRequest, 12 parseParams, 13 presentation, 14} from "./utils.ts"; 15 16const app = new Hono(); 17 18const failIfNoVM = ([vm, tag]: [VirtualMachine | undefined, string]) => 19 Effect.gen(function* () { 20 if (!vm) { 21 return yield* Effect.fail(new VmNotFoundError({ name: "unknown" })); 22 } 23 if (!vm.drivePath) { 24 return yield* Effect.fail(new ImageNotFoundError({ id: "unknown" })); 25 } 26 27 const size = yield* du(vm.drivePath); 28 29 return [vm, tag, size] as [VirtualMachine, string, number]; 30 }); 31 32app.get("/", (c) => 33 Effect.runPromise( 34 pipe( 35 listImages(), 36 presentation(c), 37 Effect.catchAll((error) => handleError(error, c)), 38 ), 39 )); 40 41app.get("/:id", (c) => 42 Effect.runPromise( 43 pipe( 44 parseParams(c), 45 Effect.flatMap(({ id }) => getImage(id)), 46 presentation(c), 47 Effect.catchAll((error) => handleError(error, c)), 48 ), 49 )); 50 51app.post("/", (c) => 52 Effect.runPromise( 53 pipe( 54 parseCreateImageRequest(c), 55 Effect.flatMap(({ from, image }) => 56 Effect.gen(function* () { 57 return yield* pipe( 58 Effect.all([getInstanceState(from), extractTag(image)]), 59 Effect.flatMap(failIfNoVM), 60 Effect.flatMap(([vm, tag, size]) => 61 saveImage({ 62 id: createId(), 63 repository: image.split(":")[0], 64 tag, 65 size, 66 path: vm.drivePath!, 67 format: vm.diskFormat, 68 }) 69 ), 70 Effect.flatMap(() => getImage(image)), 71 ); 72 }) 73 ), 74 presentation(c), 75 Effect.catchAll((error) => handleError(error, c)), 76 ), 77 )); 78 79app.delete("/:id", (c) => 80 Effect.runPromise( 81 pipe( 82 parseParams(c), 83 Effect.flatMap(({ id }) => deleteImage(id)), 84 presentation(c), 85 Effect.catchAll((error) => handleError(error, c)), 86 ), 87 )); 88 89export default app;