feat: Sprint 1 remainder — Postgres store, Docker client, warm pool, sessions route
Mirror to GitHub / mirror (push) Canceled after 0s
Mirror to GitHub / mirror (push) Canceled after 0s
- PostgresUserStore (Queryable) + migration + env-gated wiring - DockerodeClient behind injected DockerEngine - ImageManager, WarmPool, Dockerfile.wordpress, docker-compose - POST /sessions spins up sandbox (503 when unconfigured) - ADRs 0014-0015; 68 unit tests green
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import Docker from 'dockerode';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { DockerClient, SandboxInfo } from './docker-client.ts';
|
||||
|
||||
export type ContainerInspect = {
|
||||
Id: string;
|
||||
State: { Running: boolean };
|
||||
};
|
||||
|
||||
export type ContainerLike = {
|
||||
start(): Promise<void>;
|
||||
inspect(): Promise<ContainerInspect>;
|
||||
remove(opts?: { force?: boolean }): Promise<void>;
|
||||
};
|
||||
|
||||
export type DockerEngine = {
|
||||
createContainer(opts: { Image: string; name: string }): Promise<ContainerLike>;
|
||||
getContainer(id: string): ContainerLike;
|
||||
};
|
||||
|
||||
export class DockerodeClient implements DockerClient {
|
||||
constructor(private readonly engine: DockerEngine = new Docker() as unknown as DockerEngine) {}
|
||||
|
||||
async createSandbox(image: string): Promise<SandboxInfo> {
|
||||
const container = await this.engine.createContainer({ Image: image, name: `wursor-${randomUUID()}` });
|
||||
await container.start();
|
||||
const info = await container.inspect();
|
||||
return { id: info.Id, status: info.State.Running ? 'running' : 'destroyed' };
|
||||
}
|
||||
|
||||
async destroySandbox(id: string): Promise<void> {
|
||||
await this.engine.getContainer(id).remove({ force: true });
|
||||
}
|
||||
|
||||
async status(id: string): Promise<SandboxInfo | undefined> {
|
||||
try {
|
||||
const info = await this.engine.getContainer(id).inspect();
|
||||
return { id: info.Id, status: info.State.Running ? 'running' : 'destroyed' };
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export type ImageManagerOptions = {
|
||||
baseImage: string;
|
||||
webPort: number;
|
||||
};
|
||||
|
||||
export type SandboxContainerSpec = {
|
||||
image: string;
|
||||
name: string;
|
||||
ports: { container: number; host: number }[];
|
||||
labels: Record<string, string>;
|
||||
};
|
||||
|
||||
export class ImageManager {
|
||||
constructor(private readonly opts: ImageManagerOptions) {}
|
||||
|
||||
imageRef(): string {
|
||||
return this.opts.baseImage;
|
||||
}
|
||||
|
||||
containerSpec(name: string): SandboxContainerSpec {
|
||||
return {
|
||||
image: this.opts.baseImage,
|
||||
name,
|
||||
ports: [{ container: 80, host: this.opts.webPort }],
|
||||
labels: { 'wursor.managed': 'true' },
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user