Feature/sdk improvements (#2879)

* sdk improvements

* subcontainer fixes, disable wifi on migration if not in use, filterable interfaces
This commit is contained in:
Aiden McClelland
2025-04-18 14:11:13 -06:00
committed by GitHub
parent dcfbaa9243
commit 2c65033c0a
19 changed files with 426 additions and 136 deletions

View File

@@ -10,12 +10,13 @@ import {
import { Drop, splitCommand } from "../util"
import * as cp from "child_process"
import * as fs from "node:fs/promises"
import { Mounts } from "./Mounts"
export class CommandController extends Drop {
export class CommandController<Manifest extends T.SDKManifest> extends Drop {
private constructor(
readonly runningAnswer: Promise<unknown>,
private state: { exited: boolean },
private readonly subcontainer: SubContainer,
private readonly subcontainer: SubContainer<Manifest>,
private process: cp.ChildProcess,
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
) {
@@ -29,13 +30,13 @@ export class CommandController extends Drop {
imageId: keyof Manifest["images"] & T.ImageId
sharedRun?: boolean
}
| SubContainer,
| SubContainer<Manifest>,
command: T.CommandType,
options: {
subcontainerName?: string
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
sigtermTimeout?: number
mounts?: { mountpoint: string; options: MountOptions }[]
mounts: Mounts<Manifest> | null
runAsInit?: boolean
env?:
| {
@@ -65,13 +66,12 @@ export class CommandController extends Drop {
: await SubContainer.of(
effects,
subcontainer,
null,
options?.subcontainerName || commands.join(" "),
)
try {
for (let mount of options.mounts || []) {
await subc.mount(mount.options, mount.mountpoint)
}
if (options.mounts) await subc.mount(options.mounts)
let childProcess: cp.ChildProcess
if (options.runAsInit) {

View File

@@ -2,6 +2,7 @@ import * as T from "../../../base/lib/types"
import { asError } from "../../../base/lib/util/asError"
import { ExecSpawnable, MountOptions, SubContainer } from "../util/SubContainer"
import { CommandController } from "./CommandController"
import { Mounts } from "./Mounts"
const TIMEOUT_INCREMENT_MS = 1000
const MAX_TIMEOUT_MS = 30000
@@ -10,10 +11,12 @@ const MAX_TIMEOUT_MS = 30000
* and the others state of running, where it will keep a living running command
*/
export class Daemon {
private commandController: CommandController | null = null
export class Daemon<Manifest extends T.SDKManifest> {
private commandController: CommandController<Manifest> | null = null
private shouldBeRunning = false
constructor(private startCommand: () => Promise<CommandController>) {}
constructor(
private startCommand: () => Promise<CommandController<Manifest>>,
) {}
get subContainerHandle(): undefined | ExecSpawnable {
return this.commandController?.subContainerHandle
}
@@ -25,11 +28,11 @@ export class Daemon {
imageId: keyof Manifest["images"] & T.ImageId
sharedRun?: boolean
}
| SubContainer,
| SubContainer<Manifest>,
command: T.CommandType,
options: {
subcontainerName?: string
mounts?: { mountpoint: string; options: MountOptions }[]
mounts: Mounts<Manifest> | null
env?:
| {
[variable: string]: string

View File

@@ -67,7 +67,7 @@ type DaemonsParams<
*/
sharedRun?: boolean
}
| SubContainer
| SubContainer<Manifest>
/** For mounting the necessary volumes. Syntax: sdk.Mounts.of().addVolume() */
mounts: Mounts<Manifest>
env?: Record<string, string>
@@ -113,9 +113,9 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
private constructor(
readonly effects: T.Effects,
readonly started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>,
readonly daemons: Promise<Daemon>[],
readonly daemons: Promise<Daemon<Manifest>>[],
readonly ids: Ids[],
readonly healthDaemons: HealthDaemon[],
readonly healthDaemons: HealthDaemon<Manifest>[],
readonly healthChecks: HealthCheck[],
) {}
/**
@@ -164,7 +164,6 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
options.command,
{
...options,
mounts: options.mounts.build(),
subcontainerName: id,
},
)

View File

@@ -2,7 +2,7 @@ import { HealthCheckResult } from "../health/checkFns"
import { defaultTrigger } from "../trigger/defaultTrigger"
import { Ready } from "./Daemons"
import { Daemon } from "./Daemon"
import { SetHealth, Effects } from "../../../base/lib/types"
import { SetHealth, Effects, SDKManifest } from "../../../base/lib/types"
import { DEFAULT_SIGTERM_TIMEOUT } from "."
import { asError } from "../../../base/lib/util/asError"
@@ -21,7 +21,7 @@ const oncePromise = <T>() => {
* -- Running: Daemon is running and the status is in the health
*
*/
export class HealthDaemon {
export class HealthDaemon<Manifest extends SDKManifest> {
private _health: HealthCheckResult = { result: "starting", message: null }
private healthWatchers: Array<() => unknown> = []
private running = false
@@ -29,9 +29,9 @@ export class HealthDaemon {
private resolveReady: (() => void) | undefined
private readyPromise: Promise<void>
constructor(
private readonly daemon: Promise<Daemon>,
private readonly daemon: Promise<Daemon<Manifest>>,
readonly daemonIndex: number,
private readonly dependencies: HealthDaemon[],
private readonly dependencies: HealthDaemon<Manifest>[],
readonly id: string,
readonly ids: string[],
readonly ready: Ready,

View File

@@ -3,7 +3,13 @@ import { MountOptions } from "../util/SubContainer"
type MountArray = { mountpoint: string; options: MountOptions }[]
export class Mounts<Manifest extends T.SDKManifest> {
export class Mounts<
Manifest extends T.SDKManifest,
Backups extends {
subpath: string | null
mountpoint: string
} = never,
> {
private constructor(
readonly volumes: {
id: Manifest["volumes"][number]
@@ -22,10 +28,11 @@ export class Mounts<Manifest extends T.SDKManifest> {
mountpoint: string
readonly: boolean
}[],
readonly backups: Backups[],
) {}
static of<Manifest extends T.SDKManifest>() {
return new Mounts<Manifest>([], [], [])
return new Mounts<Manifest>([], [], [], [])
}
addVolume(
@@ -38,13 +45,20 @@ export class Mounts<Manifest extends T.SDKManifest> {
/** Whether or not the volume should be readonly for this daemon */
readonly: boolean,
) {
this.volumes.push({
id,
subpath,
mountpoint,
readonly,
})
return this
return new Mounts<Manifest, Backups>(
[
...this.volumes,
{
id,
subpath,
mountpoint,
readonly,
},
],
[...this.assets],
[...this.dependencies],
[...this.backups],
)
}
addAssets(
@@ -53,11 +67,18 @@ export class Mounts<Manifest extends T.SDKManifest> {
/** Where to mount the asset. e.g. /asset */
mountpoint: string,
) {
this.assets.push({
subpath,
mountpoint,
})
return this
return new Mounts<Manifest, Backups>(
[...this.volumes],
[
...this.assets,
{
subpath,
mountpoint,
},
],
[...this.dependencies],
[...this.backups],
)
}
addDependency<DependencyManifest extends T.SDKManifest>(
@@ -72,14 +93,36 @@ export class Mounts<Manifest extends T.SDKManifest> {
/** Whether or not the volume should be readonly for this daemon */
readonly: boolean,
) {
this.dependencies.push({
dependencyId,
volumeId,
subpath,
mountpoint,
readonly,
})
return this
return new Mounts<Manifest, Backups>(
[...this.volumes],
[...this.assets],
[
...this.dependencies,
{
dependencyId,
volumeId,
subpath,
mountpoint,
readonly,
},
],
[...this.backups],
)
}
addBackups(subpath: string | null, mountpoint: string) {
return new Mounts<
Manifest,
{
subpath: string | null
mountpoint: string
}
>(
[...this.volumes],
[...this.assets],
[...this.dependencies],
[...this.backups, { subpath, mountpoint }],
)
}
build(): MountArray {
@@ -130,3 +173,7 @@ export class Mounts<Manifest extends T.SDKManifest> {
)
}
}
const a = Mounts.of().addBackups(null, "")
// @ts-expect-error
const m: Mounts<T.SDKManifest, never> = a