341 lines
14 KiB
TypeScript
341 lines
14 KiB
TypeScript
import * as fs from 'fs/promises';
|
|
import * as T from '../../../base/lib/types';
|
|
import * as cp from 'child_process';
|
|
import { Buffer } from 'node:buffer';
|
|
import { Drop } from '../../../base/lib/util/Drop';
|
|
import { Mounts } from '../mainFn/Mounts';
|
|
import { BackupEffects } from '../backup/Backups';
|
|
import { PathBase } from './Volume';
|
|
export declare const execFile: typeof cp.execFile.__promisify__;
|
|
export type ExecOptions = {
|
|
input?: string | Buffer;
|
|
};
|
|
/**
|
|
* Interface representing an isolated container environment for running service processes.
|
|
*
|
|
* Provides methods for executing commands, spawning processes, mounting filesystems,
|
|
* and writing files within the container's rootfs. Comes in two flavors:
|
|
* {@link SubContainerOwned} (owns the underlying filesystem) and
|
|
* {@link SubContainerRc} (reference-counted handle to a shared container).
|
|
*/
|
|
export interface SubContainer<Manifest extends T.SDKManifest, Effects extends T.Effects = T.Effects> extends Drop, PathBase {
|
|
readonly imageId: keyof Manifest['images'] & T.ImageId;
|
|
readonly rootfs: string;
|
|
readonly guid: T.Guid;
|
|
/**
|
|
* Get the absolute path to a file or directory within this subcontainer's rootfs
|
|
* @param path Path relative to the rootfs
|
|
*/
|
|
subpath(path: string): string;
|
|
/**
|
|
* Apply filesystem mounts (volumes, assets, dependencies, backups) to this subcontainer.
|
|
* @param mounts - The Mounts configuration to apply
|
|
* @returns This subcontainer instance for chaining
|
|
*/
|
|
mount(mounts: Effects extends BackupEffects ? Mounts<Manifest, {
|
|
subpath: string | null;
|
|
mountpoint: string;
|
|
}> : Mounts<Manifest, never>): Promise<this>;
|
|
/** Destroy this subcontainer and clean up its filesystem */
|
|
destroy: () => Promise<null>;
|
|
/**
|
|
* @description run a command inside this subcontainer
|
|
* DOES NOT THROW ON NONZERO EXIT CODE (see execFail)
|
|
* @param commands an array representing the command and args to execute
|
|
* @param options
|
|
* @param timeoutMs how long to wait before killing the command in ms
|
|
* @returns
|
|
*/
|
|
exec(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{
|
|
throw: () => {
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
};
|
|
exitCode: number | null;
|
|
exitSignal: NodeJS.Signals | null;
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
}>;
|
|
/**
|
|
* @description run a command inside this subcontainer, throwing on non-zero exit status
|
|
* @param commands an array representing the command and args to execute
|
|
* @param options
|
|
* @param timeoutMs how long to wait before killing the command in ms
|
|
* @returns
|
|
*/
|
|
execFail(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
}>;
|
|
/**
|
|
* Launch a command as the init (PID 1) process of the subcontainer.
|
|
* Replaces the current leader process.
|
|
* @param command - The command and arguments to execute
|
|
* @param options - Optional environment, working directory, and user overrides
|
|
*/
|
|
launch(command: string[], options?: CommandOptions): Promise<cp.ChildProcessWithoutNullStreams>;
|
|
/**
|
|
* Spawn a command inside the subcontainer as a non-init process.
|
|
* @param command - The command and arguments to execute
|
|
* @param options - Optional environment, working directory, user, and stdio overrides
|
|
*/
|
|
spawn(command: string[], options?: CommandOptions & StdioOptions): Promise<cp.ChildProcess>;
|
|
/**
|
|
* @description Write a file to the subcontainer's filesystem
|
|
* @param path Path relative to the subcontainer rootfs (e.g. "/etc/config.json")
|
|
* @param data The data to write
|
|
* @param options Optional write options (same as node:fs/promises writeFile)
|
|
*/
|
|
writeFile(path: string, data: string | NodeJS.ArrayBufferView | Iterable<string | NodeJS.ArrayBufferView> | AsyncIterable<string | NodeJS.ArrayBufferView>, options?: Parameters<typeof fs.writeFile>[2]): Promise<void>;
|
|
/**
|
|
* Create a reference-counted handle to this subcontainer.
|
|
* The underlying container is only destroyed when all handles are released.
|
|
*/
|
|
rc(): SubContainerRc<Manifest, Effects>;
|
|
/** Returns true if this is an owned subcontainer (not a reference-counted handle) */
|
|
isOwned(): this is SubContainerOwned<Manifest, Effects>;
|
|
}
|
|
/**
|
|
* Want to limit what we can do in a container, so we want to launch a container with a specific image and the mounts.
|
|
*/
|
|
export declare class SubContainerOwned<Manifest extends T.SDKManifest, Effects extends T.Effects = T.Effects> extends Drop implements SubContainer<Manifest, Effects> {
|
|
readonly effects: Effects;
|
|
readonly imageId: keyof Manifest['images'] & T.ImageId;
|
|
readonly rootfs: string;
|
|
readonly guid: T.Guid;
|
|
private destroyed;
|
|
rcs: number;
|
|
private leader;
|
|
private leaderExited;
|
|
private waitProc;
|
|
private constructor();
|
|
static of<Manifest extends T.SDKManifest, Effects extends T.Effects>(effects: Effects, image: {
|
|
imageId: keyof Manifest['images'] & T.ImageId;
|
|
sharedRun?: boolean;
|
|
}, mounts: (Effects extends BackupEffects ? Mounts<Manifest, {
|
|
subpath: string | null;
|
|
mountpoint: string;
|
|
}> : Mounts<Manifest, never>) | null, name: string): Promise<SubContainerOwned<Manifest, Effects>>;
|
|
static withTemp<Manifest extends T.SDKManifest, T, Effects extends T.Effects>(effects: Effects, image: {
|
|
imageId: keyof Manifest['images'] & T.ImageId;
|
|
sharedRun?: boolean;
|
|
}, mounts: (Effects extends BackupEffects ? Mounts<Manifest, {
|
|
subpath: string | null;
|
|
mountpoint: string;
|
|
}> : Mounts<Manifest, never>) | null, name: string, fn: (subContainer: SubContainer<Manifest, Effects>) => Promise<T>): Promise<T>;
|
|
subpath(path: string): string;
|
|
mount(mounts: Effects extends BackupEffects ? Mounts<Manifest, {
|
|
subpath: string | null;
|
|
mountpoint: string;
|
|
}> : Mounts<Manifest, never>): Promise<this>;
|
|
private killLeader;
|
|
get destroy(): () => Promise<null>;
|
|
onDrop(): void;
|
|
/**
|
|
* @description run a command inside this subcontainer
|
|
* DOES NOT THROW ON NONZERO EXIT CODE (see execFail)
|
|
* @param commands an array representing the command and args to execute
|
|
* @param options
|
|
* @param timeoutMs how long to wait before killing the command in ms
|
|
* @returns
|
|
*/
|
|
exec(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{
|
|
throw: () => {
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
};
|
|
exitCode: number | null;
|
|
exitSignal: NodeJS.Signals | null;
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
}>;
|
|
/**
|
|
* @description run a command inside this subcontainer, throwing on non-zero exit status
|
|
* @param commands an array representing the command and args to execute
|
|
* @param options
|
|
* @param timeoutMs how long to wait before killing the command in ms
|
|
* @returns
|
|
*/
|
|
execFail(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
}>;
|
|
launch(command: string[], options?: CommandOptions): Promise<cp.ChildProcessWithoutNullStreams>;
|
|
spawn(command: string[], options?: CommandOptions & StdioOptions): Promise<cp.ChildProcess>;
|
|
/**
|
|
* @description Write a file to the subcontainer's filesystem
|
|
* @param path Path relative to the subcontainer rootfs (e.g. "/etc/config.json")
|
|
* @param data The data to write
|
|
* @param options Optional write options (same as node:fs/promises writeFile)
|
|
*/
|
|
writeFile(path: string, data: string | NodeJS.ArrayBufferView | Iterable<string | NodeJS.ArrayBufferView> | AsyncIterable<string | NodeJS.ArrayBufferView>, options?: Parameters<typeof fs.writeFile>[2]): Promise<void>;
|
|
rc(): SubContainerRc<Manifest, Effects>;
|
|
isOwned(): this is SubContainerOwned<Manifest, Effects>;
|
|
}
|
|
/**
|
|
* A reference-counted handle to a {@link SubContainerOwned}.
|
|
*
|
|
* Multiple `SubContainerRc` instances can share one underlying subcontainer.
|
|
* The subcontainer is destroyed only when the last reference is released via `destroy()`.
|
|
*/
|
|
export declare class SubContainerRc<Manifest extends T.SDKManifest, Effects extends T.Effects = T.Effects> extends Drop implements SubContainer<Manifest, Effects> {
|
|
private readonly subcontainer;
|
|
get imageId(): keyof Manifest["images"] & string;
|
|
get rootfs(): string;
|
|
get guid(): string;
|
|
subpath(path: string): string;
|
|
private destroyed;
|
|
private destroying;
|
|
constructor(subcontainer: SubContainerOwned<Manifest, Effects>);
|
|
static of<Manifest extends T.SDKManifest, Effects extends T.Effects>(effects: Effects, image: {
|
|
imageId: keyof Manifest['images'] & T.ImageId;
|
|
sharedRun?: boolean;
|
|
}, mounts: (Effects extends BackupEffects ? Mounts<Manifest, {
|
|
subpath: string | null;
|
|
mountpoint: string;
|
|
}> : Mounts<Manifest, never>) | null, name: string): Promise<SubContainerRc<Manifest, Effects>>;
|
|
static withTemp<Manifest extends T.SDKManifest, T, Effects extends T.Effects>(effects: Effects, image: {
|
|
imageId: keyof Manifest['images'] & T.ImageId;
|
|
sharedRun?: boolean;
|
|
}, mounts: (Effects extends BackupEffects ? Mounts<Manifest, {
|
|
subpath: string | null;
|
|
mountpoint: string;
|
|
}> : Mounts<Manifest, never>) | null, name: string, fn: (subContainer: SubContainer<Manifest, Effects>) => Promise<T>): Promise<T>;
|
|
mount(mounts: Effects extends BackupEffects ? Mounts<Manifest, {
|
|
subpath: string | null;
|
|
mountpoint: string;
|
|
}> : Mounts<Manifest, never>): Promise<this>;
|
|
get destroy(): () => Promise<null>;
|
|
onDrop(): void;
|
|
/**
|
|
* @description run a command inside this subcontainer
|
|
* DOES NOT THROW ON NONZERO EXIT CODE (see execFail)
|
|
* @param commands an array representing the command and args to execute
|
|
* @param options
|
|
* @param timeoutMs how long to wait before killing the command in ms
|
|
* @returns
|
|
*/
|
|
exec(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{
|
|
throw: () => {
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
};
|
|
exitCode: number | null;
|
|
exitSignal: NodeJS.Signals | null;
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
}>;
|
|
/**
|
|
* @description run a command inside this subcontainer, throwing on non-zero exit status
|
|
* @param commands an array representing the command and args to execute
|
|
* @param options
|
|
* @param timeoutMs how long to wait before killing the command in ms
|
|
* @returns
|
|
*/
|
|
execFail(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
}>;
|
|
launch(command: string[], options?: CommandOptions): Promise<cp.ChildProcessWithoutNullStreams>;
|
|
spawn(command: string[], options?: CommandOptions & StdioOptions): Promise<cp.ChildProcess>;
|
|
/**
|
|
* @description Write a file to the subcontainer's filesystem
|
|
* @param path Path relative to the subcontainer rootfs (e.g. "/etc/config.json")
|
|
* @param data The data to write
|
|
* @param options Optional write options (same as node:fs/promises writeFile)
|
|
*/
|
|
writeFile(path: string, data: string | NodeJS.ArrayBufferView | Iterable<string | NodeJS.ArrayBufferView> | AsyncIterable<string | NodeJS.ArrayBufferView>, options?: Parameters<typeof fs.writeFile>[2]): Promise<void>;
|
|
rc(): SubContainerRc<Manifest, Effects>;
|
|
isOwned(): this is SubContainerOwned<Manifest, Effects>;
|
|
}
|
|
export type CommandOptions = {
|
|
/**
|
|
* Environment variables to set for this command
|
|
*/
|
|
env?: {
|
|
[variable in string]?: string;
|
|
};
|
|
/**
|
|
* the working directory to run this command in
|
|
*/
|
|
cwd?: string;
|
|
/**
|
|
* the user to run this command as
|
|
*/
|
|
user?: string;
|
|
};
|
|
export type StdioOptions = {
|
|
stdio?: cp.IOType;
|
|
};
|
|
/** UID/GID mapping for mount id-remapping (see kernel idmappings docs) */
|
|
export type IdMap = {
|
|
fromId: number;
|
|
toId: number;
|
|
range: number;
|
|
};
|
|
/** Union of all mount option types supported by the subcontainer runtime */
|
|
export type MountOptions = MountOptionsVolume | MountOptionsAssets | MountOptionsPointer | MountOptionsBackup;
|
|
/** Mount options for binding a service volume into a subcontainer */
|
|
export type MountOptionsVolume = {
|
|
type: 'volume';
|
|
volumeId: string;
|
|
subpath: string | null;
|
|
readonly: boolean;
|
|
filetype: 'file' | 'directory' | 'infer';
|
|
idmap: IdMap[];
|
|
};
|
|
/** Mount options for binding packaged static assets into a subcontainer */
|
|
export type MountOptionsAssets = {
|
|
type: 'assets';
|
|
subpath: string | null;
|
|
filetype: 'file' | 'directory' | 'infer';
|
|
idmap: {
|
|
fromId: number;
|
|
toId: number;
|
|
range: number;
|
|
}[];
|
|
};
|
|
/** Mount options for binding a dependency package's volume into a subcontainer */
|
|
export type MountOptionsPointer = {
|
|
type: 'pointer';
|
|
packageId: string;
|
|
volumeId: string;
|
|
subpath: string | null;
|
|
readonly: boolean;
|
|
idmap: {
|
|
fromId: number;
|
|
toId: number;
|
|
range: number;
|
|
}[];
|
|
};
|
|
/** Mount options for binding the backup directory into a subcontainer */
|
|
export type MountOptionsBackup = {
|
|
type: 'backup';
|
|
subpath: string | null;
|
|
filetype: 'file' | 'directory' | 'infer';
|
|
idmap: {
|
|
fromId: number;
|
|
toId: number;
|
|
range: number;
|
|
}[];
|
|
};
|
|
/**
|
|
* Error thrown when a subcontainer command exits with a non-zero code or signal.
|
|
* Contains the full result including stdout, stderr, exit code, and exit signal.
|
|
*/
|
|
export declare class ExitError extends Error {
|
|
readonly command: string;
|
|
readonly result: {
|
|
exitCode: number | null;
|
|
exitSignal: T.Signals | null;
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
};
|
|
constructor(command: string, result: {
|
|
exitCode: number | null;
|
|
exitSignal: T.Signals | null;
|
|
stdout: string | Buffer;
|
|
stderr: string | Buffer;
|
|
});
|
|
}
|