willfire 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/isPrEventAction.d.ts +2 -0
- package/dist/cli/isPrEventAction.js +1 -0
- package/dist/cli/parseArgs.js +1 -1
- package/dist/predict/makeLiveExecutor.js +2 -1
- package/dist/sandbox/imageTag.d.ts +2 -0
- package/dist/sandbox/imageTag.js +6 -0
- package/dist/sandbox/index.d.ts +4 -0
- package/dist/sandbox/index.js +4 -0
- package/dist/sandbox/makeSandboxRunner.d.ts +15 -0
- package/dist/sandbox/makeSandboxRunner.js +44 -0
- package/dist/sandbox/runDocker.d.ts +4 -0
- package/dist/sandbox/runDocker.js +26 -0
- package/dist/sandbox/sandboxArgv.d.ts +8 -0
- package/dist/sandbox/sandboxArgv.js +41 -0
- package/dist/sandbox/sandboxConfig.d.ts +13 -0
- package/dist/sandbox/sandboxConfig.js +18 -0
- package/package.json +4 -1
- package/dist/sandbox.d.ts +0 -35
- package/dist/sandbox.js +0 -130
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isPrEventAction = (v) => v === "opened" || v === "synchronize" || v === "reopened";
|
package/dist/cli/parseArgs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isPrEventAction } from "./isPrEventAction.js";
|
|
1
2
|
const USAGE = "usage: predict --repo owner/name --pr N [--action opened|synchronize|reopened] [--json]";
|
|
2
|
-
const isPrEventAction = (v) => v === "opened" || v === "synchronize" || v === "reopened";
|
|
3
3
|
export function parseArgs(argv) {
|
|
4
4
|
const get = (flag) => {
|
|
5
5
|
const i = argv.indexOf(flag);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { makeCloneProvider, makeExecutor, makeTreeProvider, runShell, } from "../execute.js";
|
|
2
|
-
import { makeSandboxRunner
|
|
2
|
+
import { makeSandboxRunner } from "../sandbox/makeSandboxRunner.js";
|
|
3
|
+
import { SANDBOX_NODE_MAJOR } from "../sandbox/sandboxConfig.js";
|
|
3
4
|
/**
|
|
4
5
|
* The executor `predict` uses by default. Repo-authored steps run in the
|
|
5
6
|
* docker sandbox; infrastructure subprocesses (`tar`, `git`) run on the host,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/** The tag names the dockerfile that built it, so a change is a new image. */
|
|
3
|
+
export function imageTag(dockerfile) {
|
|
4
|
+
const hash = createHash("sha256").update(dockerfile).digest("hex");
|
|
5
|
+
return `willfire-sandbox:${hash.slice(0, 12)}`;
|
|
6
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `RunCommand` that runs each step inside a hermetic docker container: no
|
|
3
|
+
* network, no capabilities, a read-only root, and only the host paths in
|
|
4
|
+
* `RunSpec.mounts`, bound at their own paths. Code that can reach nothing and
|
|
5
|
+
* keep nothing needs no per-repo grant — this is what lets execution be on by
|
|
6
|
+
* default instead of configured.
|
|
7
|
+
*/
|
|
8
|
+
import type { RunCommand } from "../execute.js";
|
|
9
|
+
import { type SandboxConfig } from "./sandboxConfig.js";
|
|
10
|
+
/**
|
|
11
|
+
* Provisions the image lazily, once, and remembers a failure: every later
|
|
12
|
+
* spec gets 125 (docker's "could not start" band) with the reason rather
|
|
13
|
+
* than retrying a build that already failed.
|
|
14
|
+
*/
|
|
15
|
+
export declare function makeSandboxRunner(opts?: Partial<SandboxConfig>): RunCommand;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `RunCommand` that runs each step inside a hermetic docker container: no
|
|
3
|
+
* network, no capabilities, a read-only root, and only the host paths in
|
|
4
|
+
* `RunSpec.mounts`, bound at their own paths. Code that can reach nothing and
|
|
5
|
+
* keep nothing needs no per-repo grant — this is what lets execution be on by
|
|
6
|
+
* default instead of configured.
|
|
7
|
+
*/
|
|
8
|
+
import { imageTag } from "./imageTag.js";
|
|
9
|
+
import { runDocker } from "./runDocker.js";
|
|
10
|
+
import { sandboxArgv } from "./sandboxArgv.js";
|
|
11
|
+
import { sandboxConfig } from "./sandboxConfig.js";
|
|
12
|
+
/**
|
|
13
|
+
* Provisions the image lazily, once, and remembers a failure: every later
|
|
14
|
+
* spec gets 125 (docker's "could not start" band) with the reason rather
|
|
15
|
+
* than retrying a build that already failed.
|
|
16
|
+
*/
|
|
17
|
+
export function makeSandboxRunner(opts = {}) {
|
|
18
|
+
const cfg = sandboxConfig(opts);
|
|
19
|
+
const tag = imageTag(cfg.dockerfile);
|
|
20
|
+
let ensured = null;
|
|
21
|
+
const ensureImage = () => {
|
|
22
|
+
ensured ??= (async () => {
|
|
23
|
+
const inspect = await runDocker(cfg.dockerBin, ["image", "inspect", tag]);
|
|
24
|
+
if (inspect.code === 0) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const build = await runDocker(cfg.dockerBin, ["build", "-t", tag, "-"], cfg.dockerfile);
|
|
28
|
+
if (build.code === 0) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const trimmed = build.stderr.trim();
|
|
32
|
+
const tail = trimmed.slice(trimmed.lastIndexOf("\n") + 1);
|
|
33
|
+
return `cannot build sandbox image ${tag}${tail === "" ? "" : ` (${tail})`}`;
|
|
34
|
+
})();
|
|
35
|
+
return ensured;
|
|
36
|
+
};
|
|
37
|
+
return async (spec) => {
|
|
38
|
+
const failure = await ensureImage();
|
|
39
|
+
if (failure !== null) {
|
|
40
|
+
return { code: 125, stderr: failure };
|
|
41
|
+
}
|
|
42
|
+
return runDocker(cfg.dockerBin, sandboxArgv(spec, cfg));
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
// The client itself runs with the host environment — it needs the host PATH
|
|
3
|
+
// and any DOCKER_HOST to find the daemon.
|
|
4
|
+
export function runDocker(bin, argv, stdin) {
|
|
5
|
+
return new Promise((resolvePromise) => {
|
|
6
|
+
const child = spawn(bin, argv, {
|
|
7
|
+
env: process.env,
|
|
8
|
+
stdio: [stdin === undefined ? "ignore" : "pipe", "ignore", "pipe"],
|
|
9
|
+
});
|
|
10
|
+
let stderr = "";
|
|
11
|
+
child.stderr.on("data", (d) => {
|
|
12
|
+
stderr += String(d);
|
|
13
|
+
if (stderr.length > 4096) {
|
|
14
|
+
stderr = stderr.slice(-4096);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
child.on("spawn", () => {
|
|
18
|
+
if (stdin !== undefined) {
|
|
19
|
+
child.stdin.write(stdin);
|
|
20
|
+
child.stdin.end();
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
child.on("error", () => resolvePromise({ code: 127, stderr }));
|
|
24
|
+
child.on("close", (code) => resolvePromise({ code: code ?? 1, stderr }));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RunSpec } from "../execute.js";
|
|
2
|
+
import type { SandboxConfig } from "./sandboxConfig.js";
|
|
3
|
+
/**
|
|
4
|
+
* The complete `docker run` argv for one step. `PATH` and `HOME` in
|
|
5
|
+
* `spec.env` are host facts; the container gets its image's PATH and a
|
|
6
|
+
* writable `HOME=/tmp` instead.
|
|
7
|
+
*/
|
|
8
|
+
export declare function sandboxArgv(spec: RunSpec, cfg: SandboxConfig): string[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { imageTag } from "./imageTag.js";
|
|
2
|
+
/**
|
|
3
|
+
* The complete `docker run` argv for one step. `PATH` and `HOME` in
|
|
4
|
+
* `spec.env` are host facts; the container gets its image's PATH and a
|
|
5
|
+
* writable `HOME=/tmp` instead.
|
|
6
|
+
*/
|
|
7
|
+
export function sandboxArgv(spec, cfg) {
|
|
8
|
+
const argv = [
|
|
9
|
+
"run",
|
|
10
|
+
"--rm",
|
|
11
|
+
"--network",
|
|
12
|
+
"none",
|
|
13
|
+
"--cap-drop",
|
|
14
|
+
"ALL",
|
|
15
|
+
"--security-opt",
|
|
16
|
+
"no-new-privileges",
|
|
17
|
+
"--read-only",
|
|
18
|
+
"--tmpfs",
|
|
19
|
+
"/tmp",
|
|
20
|
+
"--user",
|
|
21
|
+
`${cfg.uid}:${cfg.gid}`,
|
|
22
|
+
];
|
|
23
|
+
for (const m of spec.mounts ?? []) {
|
|
24
|
+
argv.push("-v", `${m.path}:${m.path}${m.writable ? "" : ":ro"}`);
|
|
25
|
+
}
|
|
26
|
+
argv.push("-w", spec.cwd);
|
|
27
|
+
for (const [k, v] of Object.entries(spec.env)) {
|
|
28
|
+
if (k !== "PATH" && k !== "HOME") {
|
|
29
|
+
argv.push("-e", `${k}=${v}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
argv.push("-e", "HOME=/tmp");
|
|
33
|
+
argv.push(imageTag(cfg.dockerfile));
|
|
34
|
+
if (spec.shell === "bash") {
|
|
35
|
+
argv.push("bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "-c", spec.script);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
argv.push("sh", "-e", "-c", spec.script);
|
|
39
|
+
}
|
|
40
|
+
return argv;
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The node major the image ships — also the refusal boundary for `setup-node`
|
|
3
|
+
* and `node2x` runtimes asking for any other major.
|
|
4
|
+
*/
|
|
5
|
+
export declare const SANDBOX_NODE_MAJOR = 24;
|
|
6
|
+
export declare const DOCKERFILE = "FROM node:24-slim\nRUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates python3 && rm -rf /var/lib/apt/lists/*\n";
|
|
7
|
+
export interface SandboxConfig {
|
|
8
|
+
dockerBin: string;
|
|
9
|
+
uid: number;
|
|
10
|
+
gid: number;
|
|
11
|
+
dockerfile: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function sandboxConfig(opts?: Partial<SandboxConfig>): SandboxConfig;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The node major the image ships — also the refusal boundary for `setup-node`
|
|
3
|
+
* and `node2x` runtimes asking for any other major.
|
|
4
|
+
*/
|
|
5
|
+
export const SANDBOX_NODE_MAJOR = 24;
|
|
6
|
+
// git and python3: checkout's postcondition and the interpreters a script on
|
|
7
|
+
// a GitHub-hosted runner takes for granted.
|
|
8
|
+
export const DOCKERFILE = `FROM node:${SANDBOX_NODE_MAJOR}-slim
|
|
9
|
+
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates python3 && rm -rf /var/lib/apt/lists/*
|
|
10
|
+
`;
|
|
11
|
+
export function sandboxConfig(opts = {}) {
|
|
12
|
+
return {
|
|
13
|
+
dockerBin: opts.dockerBin ?? "docker",
|
|
14
|
+
uid: opts.uid ?? process.getuid(),
|
|
15
|
+
gid: opts.gid ?? process.getgid(),
|
|
16
|
+
dockerfile: opts.dockerfile ?? DOCKERFILE,
|
|
17
|
+
};
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "willfire",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Predict the set of CI check entries GitHub Actions will create for a pull request",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"packageManager": "pnpm@10.33.0",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc -p tsconfig.build.json",
|
|
39
|
+
"lint": "eslint .",
|
|
39
40
|
"predict": "tsx src/cli.ts",
|
|
40
41
|
"prepare": "pnpm build",
|
|
41
42
|
"test": "vitest run",
|
|
@@ -51,9 +52,11 @@
|
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@types/node": "^22.0.0",
|
|
53
54
|
"@vitest/coverage-v8": "^4.1.10",
|
|
55
|
+
"eslint": "^10.9.1",
|
|
54
56
|
"testing-conventions": "^0.0.91",
|
|
55
57
|
"tsx": "^4.19.0",
|
|
56
58
|
"typescript": "^5.6.0",
|
|
59
|
+
"typescript-eslint": "^8.68.0",
|
|
57
60
|
"vitest": "^4.1.10"
|
|
58
61
|
}
|
|
59
62
|
}
|
package/dist/sandbox.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A `RunCommand` that runs each step inside a hermetic docker container: no
|
|
3
|
-
* network, no capabilities, a read-only root, and only the host paths in
|
|
4
|
-
* `RunSpec.mounts`, bound at their own paths. Code that can reach nothing and
|
|
5
|
-
* keep nothing needs no per-repo grant — this is what lets execution be on by
|
|
6
|
-
* default instead of configured.
|
|
7
|
-
*/
|
|
8
|
-
import type { RunCommand, RunSpec } from "./execute.js";
|
|
9
|
-
/**
|
|
10
|
-
* The node major the image ships — also the refusal boundary for `setup-node`
|
|
11
|
-
* and `node2x` runtimes asking for any other major.
|
|
12
|
-
*/
|
|
13
|
-
export declare const SANDBOX_NODE_MAJOR = 24;
|
|
14
|
-
export declare const DOCKERFILE = "FROM node:24-slim\nRUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates python3 && rm -rf /var/lib/apt/lists/*\n";
|
|
15
|
-
export interface SandboxConfig {
|
|
16
|
-
dockerBin: string;
|
|
17
|
-
uid: number;
|
|
18
|
-
gid: number;
|
|
19
|
-
dockerfile: string;
|
|
20
|
-
}
|
|
21
|
-
export declare function sandboxConfig(opts?: Partial<SandboxConfig>): SandboxConfig;
|
|
22
|
-
/** The tag names the dockerfile that built it, so a change is a new image. */
|
|
23
|
-
export declare function imageTag(dockerfile: string): string;
|
|
24
|
-
/**
|
|
25
|
-
* The complete `docker run` argv for one step. `PATH` and `HOME` in
|
|
26
|
-
* `spec.env` are host facts; the container gets its image's PATH and a
|
|
27
|
-
* writable `HOME=/tmp` instead.
|
|
28
|
-
*/
|
|
29
|
-
export declare function sandboxArgv(spec: RunSpec, cfg: SandboxConfig): string[];
|
|
30
|
-
/**
|
|
31
|
-
* Provisions the image lazily, once, and remembers a failure: every later
|
|
32
|
-
* spec gets 125 (docker's "could not start" band) with the reason rather
|
|
33
|
-
* than retrying a build that already failed.
|
|
34
|
-
*/
|
|
35
|
-
export declare function makeSandboxRunner(opts?: Partial<SandboxConfig>): RunCommand;
|
package/dist/sandbox.js
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A `RunCommand` that runs each step inside a hermetic docker container: no
|
|
3
|
-
* network, no capabilities, a read-only root, and only the host paths in
|
|
4
|
-
* `RunSpec.mounts`, bound at their own paths. Code that can reach nothing and
|
|
5
|
-
* keep nothing needs no per-repo grant — this is what lets execution be on by
|
|
6
|
-
* default instead of configured.
|
|
7
|
-
*/
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
|
-
import { createHash } from "node:crypto";
|
|
10
|
-
/**
|
|
11
|
-
* The node major the image ships — also the refusal boundary for `setup-node`
|
|
12
|
-
* and `node2x` runtimes asking for any other major.
|
|
13
|
-
*/
|
|
14
|
-
export const SANDBOX_NODE_MAJOR = 24;
|
|
15
|
-
// git and python3: checkout's postcondition and the interpreters a script on
|
|
16
|
-
// a GitHub-hosted runner takes for granted.
|
|
17
|
-
export const DOCKERFILE = `FROM node:${SANDBOX_NODE_MAJOR}-slim
|
|
18
|
-
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates python3 && rm -rf /var/lib/apt/lists/*
|
|
19
|
-
`;
|
|
20
|
-
export function sandboxConfig(opts = {}) {
|
|
21
|
-
return {
|
|
22
|
-
dockerBin: opts.dockerBin ?? "docker",
|
|
23
|
-
uid: opts.uid ?? process.getuid(),
|
|
24
|
-
gid: opts.gid ?? process.getgid(),
|
|
25
|
-
dockerfile: opts.dockerfile ?? DOCKERFILE,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
/** The tag names the dockerfile that built it, so a change is a new image. */
|
|
29
|
-
export function imageTag(dockerfile) {
|
|
30
|
-
const hash = createHash("sha256").update(dockerfile).digest("hex");
|
|
31
|
-
return `willfire-sandbox:${hash.slice(0, 12)}`;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* The complete `docker run` argv for one step. `PATH` and `HOME` in
|
|
35
|
-
* `spec.env` are host facts; the container gets its image's PATH and a
|
|
36
|
-
* writable `HOME=/tmp` instead.
|
|
37
|
-
*/
|
|
38
|
-
export function sandboxArgv(spec, cfg) {
|
|
39
|
-
const argv = [
|
|
40
|
-
"run",
|
|
41
|
-
"--rm",
|
|
42
|
-
"--network",
|
|
43
|
-
"none",
|
|
44
|
-
"--cap-drop",
|
|
45
|
-
"ALL",
|
|
46
|
-
"--security-opt",
|
|
47
|
-
"no-new-privileges",
|
|
48
|
-
"--read-only",
|
|
49
|
-
"--tmpfs",
|
|
50
|
-
"/tmp",
|
|
51
|
-
"--user",
|
|
52
|
-
`${cfg.uid}:${cfg.gid}`,
|
|
53
|
-
];
|
|
54
|
-
for (const m of spec.mounts ?? []) {
|
|
55
|
-
argv.push("-v", `${m.path}:${m.path}${m.writable ? "" : ":ro"}`);
|
|
56
|
-
}
|
|
57
|
-
argv.push("-w", spec.cwd);
|
|
58
|
-
for (const [k, v] of Object.entries(spec.env)) {
|
|
59
|
-
if (k !== "PATH" && k !== "HOME") {
|
|
60
|
-
argv.push("-e", `${k}=${v}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
argv.push("-e", "HOME=/tmp");
|
|
64
|
-
argv.push(imageTag(cfg.dockerfile));
|
|
65
|
-
if (spec.shell === "bash") {
|
|
66
|
-
argv.push("bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "-c", spec.script);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
argv.push("sh", "-e", "-c", spec.script);
|
|
70
|
-
}
|
|
71
|
-
return argv;
|
|
72
|
-
}
|
|
73
|
-
// The client itself runs with the host environment — it needs the host PATH
|
|
74
|
-
// and any DOCKER_HOST to find the daemon.
|
|
75
|
-
function runDocker(bin, argv, stdin) {
|
|
76
|
-
return new Promise((resolvePromise) => {
|
|
77
|
-
const child = spawn(bin, argv, {
|
|
78
|
-
env: process.env,
|
|
79
|
-
stdio: [stdin === undefined ? "ignore" : "pipe", "ignore", "pipe"],
|
|
80
|
-
});
|
|
81
|
-
let stderr = "";
|
|
82
|
-
child.stderr.on("data", (d) => {
|
|
83
|
-
stderr += String(d);
|
|
84
|
-
if (stderr.length > 4096) {
|
|
85
|
-
stderr = stderr.slice(-4096);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
child.on("spawn", () => {
|
|
89
|
-
if (stdin !== undefined) {
|
|
90
|
-
child.stdin.write(stdin);
|
|
91
|
-
child.stdin.end();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
child.on("error", () => resolvePromise({ code: 127, stderr }));
|
|
95
|
-
child.on("close", (code) => resolvePromise({ code: code ?? 1, stderr }));
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Provisions the image lazily, once, and remembers a failure: every later
|
|
100
|
-
* spec gets 125 (docker's "could not start" band) with the reason rather
|
|
101
|
-
* than retrying a build that already failed.
|
|
102
|
-
*/
|
|
103
|
-
export function makeSandboxRunner(opts = {}) {
|
|
104
|
-
const cfg = sandboxConfig(opts);
|
|
105
|
-
const tag = imageTag(cfg.dockerfile);
|
|
106
|
-
let ensured = null;
|
|
107
|
-
const ensureImage = () => {
|
|
108
|
-
ensured ??= (async () => {
|
|
109
|
-
const inspect = await runDocker(cfg.dockerBin, ["image", "inspect", tag]);
|
|
110
|
-
if (inspect.code === 0) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
const build = await runDocker(cfg.dockerBin, ["build", "-t", tag, "-"], cfg.dockerfile);
|
|
114
|
-
if (build.code === 0) {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
const trimmed = build.stderr.trim();
|
|
118
|
-
const tail = trimmed.slice(trimmed.lastIndexOf("\n") + 1);
|
|
119
|
-
return `cannot build sandbox image ${tag}${tail === "" ? "" : ` (${tail})`}`;
|
|
120
|
-
})();
|
|
121
|
-
return ensured;
|
|
122
|
-
};
|
|
123
|
-
return async (spec) => {
|
|
124
|
-
const failure = await ensureImage();
|
|
125
|
-
if (failure !== null) {
|
|
126
|
-
return { code: 125, stderr: failure };
|
|
127
|
-
}
|
|
128
|
-
return runDocker(cfg.dockerBin, sandboxArgv(spec, cfg));
|
|
129
|
-
};
|
|
130
|
-
}
|