mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 22:39:46 +00:00
Fix/overlay destroy (#2707)
* feature: Make all errors in console.error be including an error for that stack tract * feature: Make all errors in console.error be including an error for that stack tract * fix: Add the tinisubreaper for the subreapers to know they are not the reaper * fix: overlay always destroyed * chore: Move the style of destroy to just private
This commit is contained in:
@@ -5,7 +5,40 @@ import { promisify } from "util"
|
||||
import { Buffer } from "node:buffer"
|
||||
export const execFile = promisify(cp.execFile)
|
||||
const WORKDIR = (imageId: string) => `/media/startos/images/${imageId}/`
|
||||
export class Overlay {
|
||||
|
||||
type ExecResults = {
|
||||
exitCode: number | null
|
||||
exitSignal: NodeJS.Signals | null
|
||||
stdout: string | Buffer
|
||||
stderr: string | Buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the type that is going to describe what an overlay could do. The main point of the
|
||||
* overlay is to have commands that run in a chrooted environment. This is useful for running
|
||||
* commands in a containerized environment. But, I wanted the destroy to sometimes be doable, for example the
|
||||
* case where the overlay isn't owned by the process, the overlay shouldn't be destroyed.
|
||||
*/
|
||||
export interface ExecSpawnable {
|
||||
get destroy(): undefined | (() => Promise<void>)
|
||||
exec(
|
||||
command: string[],
|
||||
options?: CommandOptions,
|
||||
timeoutMs?: number | null,
|
||||
): Promise<ExecResults>
|
||||
spawn(
|
||||
command: string[],
|
||||
options?: CommandOptions,
|
||||
): Promise<cp.ChildProcessWithoutNullStreams>
|
||||
}
|
||||
/**
|
||||
* Want to limit what we can do in a container, so we want to launch a container with a specific image and the mounts.
|
||||
*
|
||||
* Implements:
|
||||
* @see {@link ExecSpawnable}
|
||||
*/
|
||||
export class Overlay implements ExecSpawnable {
|
||||
private destroyed = false
|
||||
private constructor(
|
||||
readonly effects: T.Effects,
|
||||
readonly imageId: T.ImageId,
|
||||
@@ -39,23 +72,6 @@ export class Overlay {
|
||||
return new Overlay(effects, id, rootfs, guid)
|
||||
}
|
||||
|
||||
static async with<T>(
|
||||
effects: T.Effects,
|
||||
image: { id: T.ImageId; sharedRun?: boolean },
|
||||
mounts: { options: MountOptions; path: string }[],
|
||||
fn: (overlay: Overlay) => Promise<T>,
|
||||
): Promise<T> {
|
||||
const overlay = await Overlay.of(effects, image)
|
||||
try {
|
||||
for (let mount of mounts) {
|
||||
await overlay.mount(mount.options, mount.path)
|
||||
}
|
||||
return await fn(overlay)
|
||||
} finally {
|
||||
await overlay.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
async mount(options: MountOptions, path: string): Promise<Overlay> {
|
||||
path = path.startsWith("/")
|
||||
? `${this.rootfs}${path}`
|
||||
@@ -70,7 +86,7 @@ export class Overlay {
|
||||
|
||||
await fs.mkdir(from, { recursive: true })
|
||||
await fs.mkdir(path, { recursive: true })
|
||||
await await execFile("mount", ["--bind", from, path])
|
||||
await execFile("mount", ["--bind", from, path])
|
||||
} else if (options.type === "assets") {
|
||||
const subpath = options.subpath
|
||||
? options.subpath.startsWith("/")
|
||||
@@ -101,10 +117,14 @@ export class Overlay {
|
||||
return this
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
const imageId = this.imageId
|
||||
const guid = this.guid
|
||||
await this.effects.destroyOverlayedImage({ guid })
|
||||
get destroy() {
|
||||
return async () => {
|
||||
if (this.destroyed) return
|
||||
this.destroyed = true
|
||||
const imageId = this.imageId
|
||||
const guid = this.guid
|
||||
await this.effects.destroyOverlayedImage({ guid })
|
||||
}
|
||||
}
|
||||
|
||||
async exec(
|
||||
@@ -218,6 +238,32 @@ export class Overlay {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an overlay but remove the ability to add the mounts and the destroy function.
|
||||
* Lets other functions, like health checks, to not destroy the parents.
|
||||
*
|
||||
*/
|
||||
export class NonDestroyableOverlay implements ExecSpawnable {
|
||||
constructor(private overlay: ExecSpawnable) {}
|
||||
get destroy() {
|
||||
return undefined
|
||||
}
|
||||
|
||||
exec(
|
||||
command: string[],
|
||||
options?: CommandOptions,
|
||||
timeoutMs?: number | null,
|
||||
): Promise<ExecResults> {
|
||||
return this.overlay.exec(command, options, timeoutMs)
|
||||
}
|
||||
spawn(
|
||||
command: string[],
|
||||
options?: CommandOptions,
|
||||
): Promise<cp.ChildProcessWithoutNullStreams> {
|
||||
return this.overlay.spawn(command, options)
|
||||
}
|
||||
}
|
||||
|
||||
export type CommandOptions = {
|
||||
env?: { [variable: string]: string }
|
||||
cwd?: string
|
||||
|
||||
6
sdk/lib/util/asError.ts
Normal file
6
sdk/lib/util/asError.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const asError = (e: unknown) => {
|
||||
if (e instanceof Error) {
|
||||
return new Error(e as any)
|
||||
}
|
||||
return new Error(`${e}`)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import "./Overlay"
|
||||
import "./once"
|
||||
|
||||
export { GetServiceInterface, getServiceInterface } from "./getServiceInterface"
|
||||
export { asError } from "./asError"
|
||||
export { getServiceInterfaces } from "./getServiceInterfaces"
|
||||
export { addressHostToUrl } from "./getServiceInterface"
|
||||
export { hostnameInfoToAddress } from "./Hostname"
|
||||
|
||||
Reference in New Issue
Block a user