From 884c1c01d4b6b9a3d4ca31d355409f19b7627068 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Mon, 22 Jan 2024 22:28:23 +1100 Subject: [PATCH] Add layering for OCI (#290) --- packages/runtime/lib/elements/container.ts | 9 ++++++- packages/runtime/lib/elements/terminal.ts | 9 ++++++- packages/runtime/lib/elements/wasi.ts | 9 ++++++- packages/runtime/lib/oci.ts | 31 +++++++++++++++++++--- packages/runtime/lib/provider.ts | 18 +++++++++++-- 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/packages/runtime/lib/elements/container.ts b/packages/runtime/lib/elements/container.ts index 05866b3..93ecbed 100644 --- a/packages/runtime/lib/elements/container.ts +++ b/packages/runtime/lib/elements/container.ts @@ -211,7 +211,14 @@ export class ContainerElement extends HTMLElement { return { resultType: "terminated" }; } console.error(e); - this.terminal.write(`\nRunno crashed: ${e}`); + if ( + e != null && + (e instanceof Error || (typeof e === "object" && "message" in e)) + ) { + this.terminal.write( + `\n[Crashed] ${(e as any).type ?? "Error"}: ${e.message}\n` + ); + } return { resultType: "crash", error: makeRunnoError(e) }; } finally { this.workerHost = undefined; diff --git a/packages/runtime/lib/elements/terminal.ts b/packages/runtime/lib/elements/terminal.ts index f63ee8a..d8d0bb8 100644 --- a/packages/runtime/lib/elements/terminal.ts +++ b/packages/runtime/lib/elements/terminal.ts @@ -120,7 +120,14 @@ export class TerminalElement extends HTMLElement { return { resultType: "terminated" }; } console.error(e); - this.terminal.write(`\nRunno crashed: ${e}`); + if ( + e != null && + (e instanceof Error || (typeof e === "object" && "message" in e)) + ) { + this.terminal.write( + `\n[Crashed] ${(e as any).type ?? "Error"}: ${e.message}\n` + ); + } return { resultType: "crash", error: makeRunnoError(e) }; } finally { this.workerHost = undefined; diff --git a/packages/runtime/lib/elements/wasi.ts b/packages/runtime/lib/elements/wasi.ts index 585f2db..406e343 100644 --- a/packages/runtime/lib/elements/wasi.ts +++ b/packages/runtime/lib/elements/wasi.ts @@ -227,7 +227,14 @@ export class WASIElement extends HTMLElement { return { resultType: "terminated" }; } console.error(e); - this.terminal.write(`\nRunno crashed: ${e}`); + if ( + e != null && + (e instanceof Error || (typeof e === "object" && "message" in e)) + ) { + this.terminal.write( + `\n[Crashed] ${(e as any).type ?? "Error"}: ${e.message}\n` + ); + } return { resultType: "crash", error: makeRunnoError(e) }; } finally { this.workerHost = undefined; diff --git a/packages/runtime/lib/oci.ts b/packages/runtime/lib/oci.ts index 9760724..a4604b7 100644 --- a/packages/runtime/lib/oci.ts +++ b/packages/runtime/lib/oci.ts @@ -11,11 +11,9 @@ type OCIContext = { export async function extractOCIFile(binary: Uint8Array): Promise { const contents = await extractTarGz(binary); - // TODO: throw if the file doesn't exist or whatever const manifestFile = contents["/manifest.json"]; const manifest = fileToJSON(manifestFile)[0]; - // TODO: throw if the file doesn't exist or whatever const configPath = manifest["Config"]; const config = fileToJSON(contents[`/${configPath}`]); @@ -33,9 +31,34 @@ export async function extractOCIFile(binary: Uint8Array): Promise { }) ); - // TODO: Implement the OCI spec for layering + // OCI spec for layering includes whiteout / deleting files // https://github.com/opencontainers/image-spec/blob/main/layer.md - const fs = layers.reduce((prev, current) => ({ ...prev, ...current }), {}); + const fs = layers.reduce((prev, current) => { + // Whiteout files must be applied first + + const entries = Object.entries(current); + for (const [path] of entries) { + if (path.endsWith(".wh..wh..opq")) { + // Opaque whiteout, delete all siblings + const prefix = path.replace(".wh..wh..opq", ""); + for (const [existingPath] of Object.entries(prev)) { + if (existingPath.startsWith(prefix)) { + delete prev[existingPath]; + } + } + } else if (path.includes(".wh.")) { + // Regular whiteout - just delete this one file + const filename = path.replace(".wh.", ""); + delete prev[filename]; + } + } + + const currentWithoutWhiteouts = Object.fromEntries( + entries.filter(([p]) => !p.includes(".wh.")) + ); + + return { ...prev, ...currentWithoutWhiteouts }; + }, {}); return { fs, diff --git a/packages/runtime/lib/provider.ts b/packages/runtime/lib/provider.ts index 5ff1126..60f50aa 100644 --- a/packages/runtime/lib/provider.ts +++ b/packages/runtime/lib/provider.ts @@ -81,7 +81,14 @@ export class RunnoProvider implements RuntimeMethods { fs = prepare.fs; } catch (e) { console.error(e); - this.terminal.terminal.write(`\nRunno crashed: ${e}\n`); + if ( + e != null && + (e instanceof Error || (typeof e === "object" && "message" in e)) + ) { + this.terminal.terminal.write( + `\n[Crashed] ${(e as any).type ?? "Error"}: ${e.message}\n` + ); + } return { resultType: "crash", @@ -102,7 +109,14 @@ export class RunnoProvider implements RuntimeMethods { fs = { ...fs, ...baseFS }; } catch (e) { console.error(e); - this.terminal.terminal.write(`\nRunno crashed: ${e}\n`); + if ( + e != null && + (e instanceof Error || (typeof e === "object" && "message" in e)) + ) { + this.terminal.terminal.write( + `\n[Crashed] ${(e as any).type ?? "Error"}: ${e.message}\n` + ); + } return { resultType: "crash",