@mastra/docker 0.9.0-alpha.0 → 0.9.0-alpha.2
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/README.md +8 -0
- package/dist/abort.d.ts +7 -0
- package/dist/abort.d.ts.map +1 -0
- package/dist/index.cjs +211 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +211 -47
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +8 -3
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/kill-helper-exit.fixture.d.ts +2 -0
- package/dist/sandbox/kill-helper-exit.fixture.d.ts.map +1 -0
- package/dist/sandbox/process-manager.d.ts.map +1 -1
- package/dist/template/build-session.d.ts +1 -1
- package/dist/template/build-session.d.ts.map +1 -1
- package/dist/template/index.d.ts +1 -1
- package/dist/template/index.d.ts.map +1 -1
- package/dist/template/repo-template.d.ts +8 -4
- package/dist/template/repo-template.d.ts.map +1 -1
- package/dist/template/template.d.ts +2 -0
- package/dist/template/template.d.ts.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -91,6 +91,11 @@ const b = new DockerSandbox({ template });
|
|
|
91
91
|
const result = await template.build();
|
|
92
92
|
if (result.status !== 'ready') throw new Error(result.error);
|
|
93
93
|
|
|
94
|
+
// Cancel repository resolution or template preparation when needed.
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
await a.start({ abortSignal: controller.signal });
|
|
97
|
+
// Explicit builds accept the same option: template.build({ abortSignal: controller.signal }).
|
|
98
|
+
|
|
94
99
|
// Remove the built image when done (independent of any sandbox's destroy()).
|
|
95
100
|
await template.dispose();
|
|
96
101
|
```
|
|
@@ -105,6 +110,9 @@ template, and their signatures match the E2B and platform template builders.
|
|
|
105
110
|
The image tag is content-addressed (`mastra-template:<hash>`), so `build()` is
|
|
106
111
|
idempotent and reuses an existing image unless you pass `{ force: true }`,
|
|
107
112
|
which also bypasses the daemon's layer cache so every step really re-runs.
|
|
113
|
+
Concurrent callers share preparation work, but each caller can cancel its own
|
|
114
|
+
wait independently; the underlying build is cancelled only after its last
|
|
115
|
+
active caller aborts.
|
|
108
116
|
|
|
109
117
|
Never put secrets in `setEnvs` — they are baked into the image. For a step that
|
|
110
118
|
needs a credential, use `runWithSecrets`: the command runs in a throwaway build
|
package/dist/abort.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SandboxAbortError } from '@mastra/core/workspace';
|
|
2
|
+
export declare function createAbortError(signal: AbortSignal, operation: string): SandboxAbortError;
|
|
3
|
+
export declare function throwIfAborted(signal: AbortSignal | undefined, operation: string): void;
|
|
4
|
+
export declare function isAbortError(error: unknown): boolean;
|
|
5
|
+
export declare function normalizeAbortError(error: unknown, operation: string): unknown;
|
|
6
|
+
export declare function waitForAbortable<T>(promise: Promise<T>, signal: AbortSignal | undefined, operation: string, onAbort?: () => void): Promise<T>;
|
|
7
|
+
//# sourceMappingURL=abort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,iBAAiB,CAE1F;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAEvF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKpD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAM9E;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,IAAI,GACnB,OAAO,CAAC,CAAC,CAAC,CAqBZ"}
|
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,38 @@ let path_posix = require("path/posix");
|
|
|
32
32
|
path_posix = __toESM(path_posix, 1);
|
|
33
33
|
let _grpc_grpc_js = require("@grpc/grpc-js");
|
|
34
34
|
let child_process = require("child_process");
|
|
35
|
+
//#region src/abort.ts
|
|
36
|
+
function createAbortError(signal, operation) {
|
|
37
|
+
return new _mastra_core_workspace.SandboxAbortError(operation, signal.reason);
|
|
38
|
+
}
|
|
39
|
+
function throwIfAborted(signal, operation) {
|
|
40
|
+
if (signal?.aborted) throw createAbortError(signal, operation);
|
|
41
|
+
}
|
|
42
|
+
function isAbortError(error) {
|
|
43
|
+
return error instanceof _mastra_core_workspace.SandboxAbortError || error instanceof Error && (error.name === "AbortError" || "code" in error && error.code === "ABORT_ERR");
|
|
44
|
+
}
|
|
45
|
+
function normalizeAbortError(error, operation) {
|
|
46
|
+
return error instanceof _mastra_core_workspace.SandboxAbortError ? error : isAbortError(error) ? new _mastra_core_workspace.SandboxAbortError(operation, error) : error;
|
|
47
|
+
}
|
|
48
|
+
function waitForAbortable(promise, signal, operation, onAbort) {
|
|
49
|
+
throwIfAborted(signal, operation);
|
|
50
|
+
if (!signal) return promise;
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const abort = () => {
|
|
53
|
+
onAbort?.();
|
|
54
|
+
reject(new _mastra_core_workspace.SandboxAbortError(operation, signal.reason));
|
|
55
|
+
};
|
|
56
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
57
|
+
promise.then((value) => {
|
|
58
|
+
signal.removeEventListener("abort", abort);
|
|
59
|
+
resolve(value);
|
|
60
|
+
}, (error) => {
|
|
61
|
+
signal.removeEventListener("abort", abort);
|
|
62
|
+
reject(normalizeAbortError(error, operation));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
35
67
|
//#region src/sandbox/process-manager.ts
|
|
36
68
|
/**
|
|
37
69
|
* Docker Process Manager
|
|
@@ -204,16 +236,20 @@ var DockerProcessHandle = class extends _mastra_core_workspace.ProcessHandle {
|
|
|
204
236
|
AttachStdout: false,
|
|
205
237
|
AttachStderr: false
|
|
206
238
|
});
|
|
207
|
-
await killExec.start({});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
239
|
+
const killStream = await killExec.start({});
|
|
240
|
+
try {
|
|
241
|
+
let killInfo = await killExec.inspect();
|
|
242
|
+
while (killInfo.Running) {
|
|
243
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
244
|
+
killInfo = await killExec.inspect();
|
|
245
|
+
}
|
|
246
|
+
if (killInfo.ExitCode !== 0) throw new Error(`kill helper exited with code ${killInfo.ExitCode}`);
|
|
247
|
+
this._killed = true;
|
|
248
|
+
this._destroyStream();
|
|
249
|
+
return true;
|
|
250
|
+
} finally {
|
|
251
|
+
killStream.destroy();
|
|
212
252
|
}
|
|
213
|
-
if (killInfo.ExitCode !== 0) throw new Error(`kill helper exited with code ${killInfo.ExitCode}`);
|
|
214
|
-
this._killed = true;
|
|
215
|
-
this._destroyStream();
|
|
216
|
-
return true;
|
|
217
253
|
} catch (error) {
|
|
218
254
|
const msg = error instanceof Error ? error.message.toLowerCase() : "";
|
|
219
255
|
if (!msg.includes("no such process") && !msg.includes("esrch")) console.warn(`[DockerProcessManager] kill(${this.pid}) failed unexpectedly:`, error);
|
|
@@ -270,6 +306,7 @@ var DockerProcessManager = class extends _mastra_core_workspace.SandboxProcessMa
|
|
|
270
306
|
const container = this.container;
|
|
271
307
|
const pgidFile = `${PROC_DIR}/${(0, crypto.randomUUID)()}`;
|
|
272
308
|
const envArray = Object.entries({ ...options.env }).filter((entry) => entry[1] !== void 0).map(([k, v]) => `${k}=${v}`);
|
|
309
|
+
const attachStdin = options.stdinMode !== "ignore";
|
|
273
310
|
const exec = await container.exec({
|
|
274
311
|
Cmd: [
|
|
275
312
|
"sh",
|
|
@@ -281,17 +318,17 @@ var DockerProcessManager = class extends _mastra_core_workspace.SandboxProcessMa
|
|
|
281
318
|
],
|
|
282
319
|
AttachStdout: true,
|
|
283
320
|
AttachStderr: true,
|
|
284
|
-
AttachStdin:
|
|
321
|
+
AttachStdin: attachStdin,
|
|
285
322
|
Tty: false,
|
|
286
323
|
Env: envArray.length > 0 ? envArray : void 0,
|
|
287
324
|
WorkingDir: options.cwd
|
|
288
325
|
});
|
|
289
326
|
const stream = await exec.start({
|
|
290
327
|
hijack: true,
|
|
291
|
-
stdin:
|
|
328
|
+
stdin: attachStdin
|
|
292
329
|
});
|
|
293
330
|
const startTime = Date.now();
|
|
294
|
-
const handle = new DockerProcessHandle(exec, container, startTime, stream, pgidFile, options);
|
|
331
|
+
const handle = new DockerProcessHandle(exec, container, startTime, attachStdin ? stream : null, pgidFile, options);
|
|
295
332
|
handle._setExecStream(stream);
|
|
296
333
|
const waitPromise = new Promise((resolve) => {
|
|
297
334
|
const buffer = [];
|
|
@@ -564,7 +601,9 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
564
601
|
if (!this._container) throw new _mastra_core_workspace.SandboxNotReadyError(this.id);
|
|
565
602
|
return this._container;
|
|
566
603
|
}
|
|
567
|
-
async start() {
|
|
604
|
+
async start(options = {}) {
|
|
605
|
+
const { abortSignal } = options;
|
|
606
|
+
throwIfAborted(abortSignal, "start Docker sandbox");
|
|
568
607
|
this.logger.debug(`${LOG_PREFIX} Starting sandbox ${this.id}...`);
|
|
569
608
|
const existing = await this._findExistingContainer();
|
|
570
609
|
if (existing) {
|
|
@@ -585,7 +624,7 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
585
624
|
return;
|
|
586
625
|
}
|
|
587
626
|
this._warnOnPrivilegedHardeningConflict(this._privileged);
|
|
588
|
-
await this._resolveTemplate();
|
|
627
|
+
await this._resolveTemplate(abortSignal);
|
|
589
628
|
await this._ensureImage();
|
|
590
629
|
const envArray = Object.entries(this._env).map(([k, v]) => `${k}=${v}`);
|
|
591
630
|
const binds = Object.entries(this._volumes).map(([host, container]) => `${host}:${container}`);
|
|
@@ -845,10 +884,20 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
845
884
|
* creates a container, so a resolver-form template re-resolves each time.
|
|
846
885
|
* Adopts the template's workdir unless the sandbox was given one explicitly.
|
|
847
886
|
*/
|
|
848
|
-
async _resolveTemplate() {
|
|
887
|
+
async _resolveTemplate(abortSignal) {
|
|
849
888
|
if (!this._templateSpec) return;
|
|
850
|
-
|
|
851
|
-
|
|
889
|
+
throwIfAborted(abortSignal, "start Docker sandbox");
|
|
890
|
+
let template;
|
|
891
|
+
try {
|
|
892
|
+
template = typeof this._templateSpec === "function" ? await this._templateSpec({ abortSignal }) : this._templateSpec;
|
|
893
|
+
} catch (error) {
|
|
894
|
+
throw normalizeAbortError(error, "start Docker sandbox");
|
|
895
|
+
}
|
|
896
|
+
throwIfAborted(abortSignal, "start Docker sandbox");
|
|
897
|
+
const result = await template.build({
|
|
898
|
+
docker: this._docker,
|
|
899
|
+
abortSignal
|
|
900
|
+
});
|
|
852
901
|
if (result.status !== "ready") throw new _mastra_core_workspace.SandboxError(`Docker template build failed: ${result.error ?? "unknown error"}`, "START_FAILED", {
|
|
853
902
|
templateId: result.templateId,
|
|
854
903
|
reason: "template_build_failed"
|
|
@@ -1221,9 +1270,17 @@ const AUTH_CREDENTIALS_METHOD = "/moby.filesync.v1.Auth/Credentials";
|
|
|
1221
1270
|
* Open a session that answers `GetSecret` from `secrets`. The returned id must
|
|
1222
1271
|
* be sent as the `session` query parameter of the build request.
|
|
1223
1272
|
*/
|
|
1224
|
-
function openBuildSession(docker, secrets) {
|
|
1273
|
+
function openBuildSession(docker, secrets, abortSignal) {
|
|
1274
|
+
throwIfAborted(abortSignal, "build Docker template");
|
|
1225
1275
|
const id = (0, crypto.randomUUID)();
|
|
1226
1276
|
return new Promise((resolve, reject) => {
|
|
1277
|
+
let settled = false;
|
|
1278
|
+
const abort = () => {
|
|
1279
|
+
if (settled || !abortSignal) return;
|
|
1280
|
+
settled = true;
|
|
1281
|
+
reject(createAbortError(abortSignal, "build Docker template"));
|
|
1282
|
+
};
|
|
1283
|
+
abortSignal?.addEventListener("abort", abort, { once: true });
|
|
1227
1284
|
docker.modem.dial({
|
|
1228
1285
|
method: "POST",
|
|
1229
1286
|
path: "/session",
|
|
@@ -1239,8 +1296,14 @@ function openBuildSession(docker, secrets) {
|
|
|
1239
1296
|
500: "server error"
|
|
1240
1297
|
}
|
|
1241
1298
|
}, (err, socket) => {
|
|
1299
|
+
if (settled) {
|
|
1300
|
+
socket?.end?.();
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
settled = true;
|
|
1304
|
+
abortSignal?.removeEventListener("abort", abort);
|
|
1242
1305
|
if (err) {
|
|
1243
|
-
reject(err);
|
|
1306
|
+
reject(abortSignal?.aborted ? createAbortError(abortSignal, "build Docker template") : err);
|
|
1244
1307
|
return;
|
|
1245
1308
|
}
|
|
1246
1309
|
const server = new _grpc_grpc_js.Server();
|
|
@@ -1561,7 +1624,8 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1561
1624
|
#secrets;
|
|
1562
1625
|
#docker;
|
|
1563
1626
|
#built = false;
|
|
1564
|
-
#
|
|
1627
|
+
#queueTail;
|
|
1628
|
+
#plainBuild;
|
|
1565
1629
|
constructor(options = {}, state) {
|
|
1566
1630
|
this.#baseImage = state ? state.baseImage : validateString(options.baseImage ?? "node:22-slim", "baseImage");
|
|
1567
1631
|
this.#operations = state?.operations ?? [];
|
|
@@ -1708,19 +1772,49 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1708
1772
|
* `options.secrets`, the template's `secrets` option, or `process.env`.
|
|
1709
1773
|
*/
|
|
1710
1774
|
async build(options = {}) {
|
|
1711
|
-
|
|
1712
|
-
const
|
|
1713
|
-
this.#
|
|
1714
|
-
|
|
1715
|
-
|
|
1775
|
+
throwIfAborted(options.abortSignal, "build Docker template");
|
|
1776
|
+
const isPlain = !options.force && options.secrets === void 0 && options.docker === void 0;
|
|
1777
|
+
if (isPlain && this.#plainBuild) return this.#waitForBuild(this.#plainBuild, options.abortSignal);
|
|
1778
|
+
const previous = this.#queueTail?.promise.catch(() => void 0) ?? Promise.resolve();
|
|
1779
|
+
const controller = new AbortController();
|
|
1780
|
+
const run = {
|
|
1781
|
+
controller,
|
|
1782
|
+
waiters: 0,
|
|
1783
|
+
promise: previous.then(() => {
|
|
1784
|
+
throwIfAborted(controller.signal, "build Docker template");
|
|
1785
|
+
return this.#build({
|
|
1786
|
+
...options,
|
|
1787
|
+
abortSignal: controller.signal
|
|
1788
|
+
});
|
|
1789
|
+
})
|
|
1790
|
+
};
|
|
1791
|
+
this.#queueTail = run;
|
|
1792
|
+
if (isPlain) this.#plainBuild = run;
|
|
1793
|
+
run.promise.finally(() => {
|
|
1794
|
+
if (this.#queueTail === run) this.#queueTail = void 0;
|
|
1795
|
+
if (this.#plainBuild === run) this.#plainBuild = void 0;
|
|
1716
1796
|
}).catch(() => void 0);
|
|
1717
|
-
return run;
|
|
1797
|
+
return this.#waitForBuild(run, options.abortSignal);
|
|
1798
|
+
}
|
|
1799
|
+
#waitForBuild(run, abortSignal) {
|
|
1800
|
+
run.waiters += 1;
|
|
1801
|
+
let settled = false;
|
|
1802
|
+
const release = (aborted) => {
|
|
1803
|
+
if (settled) return;
|
|
1804
|
+
settled = true;
|
|
1805
|
+
run.waiters -= 1;
|
|
1806
|
+
if (aborted && run.waiters === 0) run.controller.abort();
|
|
1807
|
+
};
|
|
1808
|
+
return waitForAbortable(run.promise, abortSignal, "build Docker template", () => release(true)).finally(() => release(false));
|
|
1718
1809
|
}
|
|
1719
1810
|
async #build(options) {
|
|
1720
1811
|
const docker = options.docker ?? this.#getDocker();
|
|
1721
1812
|
const tag = this.templateId;
|
|
1813
|
+
const { abortSignal } = options;
|
|
1814
|
+
throwIfAborted(abortSignal, "build Docker template");
|
|
1722
1815
|
if (!options.force) try {
|
|
1723
1816
|
await docker.getImage(tag).inspect();
|
|
1817
|
+
throwIfAborted(abortSignal, "build Docker template");
|
|
1724
1818
|
this.#built = true;
|
|
1725
1819
|
return {
|
|
1726
1820
|
status: "ready",
|
|
@@ -1730,24 +1824,37 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1730
1824
|
if (!isImageNotFoundError(error)) throw error;
|
|
1731
1825
|
}
|
|
1732
1826
|
const secrets = await this.#resolveSecrets(options.secrets);
|
|
1827
|
+
throwIfAborted(abortSignal, "build Docker template");
|
|
1733
1828
|
const context = (0, tar_stream.pack)();
|
|
1734
1829
|
context.entry({ name: "Dockerfile" }, this.dockerfile);
|
|
1735
1830
|
context.finalize();
|
|
1736
1831
|
try {
|
|
1737
1832
|
const nocache = options.force === true;
|
|
1738
1833
|
if (secrets) {
|
|
1739
|
-
const { stream, session } = await this.#buildWithSecrets(docker, context, tag, secrets, nocache);
|
|
1834
|
+
const { stream, session } = await this.#buildWithSecrets(docker, context, tag, secrets, nocache, abortSignal);
|
|
1740
1835
|
try {
|
|
1741
|
-
await this.#followBuild(docker, stream);
|
|
1836
|
+
await this.#followBuild(docker, stream, abortSignal);
|
|
1742
1837
|
} finally {
|
|
1743
1838
|
session.close();
|
|
1744
1839
|
}
|
|
1745
|
-
} else
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1840
|
+
} else {
|
|
1841
|
+
const stream = await waitForAbortable(docker.buildImage(context, {
|
|
1842
|
+
t: tag,
|
|
1843
|
+
nocache
|
|
1844
|
+
}).then((stream) => {
|
|
1845
|
+
if (abortSignal?.aborted) {
|
|
1846
|
+
stream.destroy?.();
|
|
1847
|
+
throw createAbortError(abortSignal, "build Docker template");
|
|
1848
|
+
}
|
|
1849
|
+
return stream;
|
|
1850
|
+
}), abortSignal, "build Docker template", () => {
|
|
1851
|
+
context.destroy?.();
|
|
1852
|
+
});
|
|
1853
|
+
await this.#followBuild(docker, stream, abortSignal);
|
|
1854
|
+
}
|
|
1749
1855
|
} catch (error) {
|
|
1750
1856
|
this.#built = false;
|
|
1857
|
+
if (abortSignal?.aborted) throw normalizeAbortError(error, "build Docker template");
|
|
1751
1858
|
return {
|
|
1752
1859
|
status: "failed",
|
|
1753
1860
|
templateId: tag,
|
|
@@ -1778,12 +1885,35 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1778
1885
|
* directly because `docker.buildImage` replaces any session id with its own
|
|
1779
1886
|
* auth-only session when `version` is `'2'`.
|
|
1780
1887
|
*/
|
|
1781
|
-
async #buildWithSecrets(docker, context, tag, secrets, nocache) {
|
|
1782
|
-
|
|
1888
|
+
async #buildWithSecrets(docker, context, tag, secrets, nocache, abortSignal) {
|
|
1889
|
+
throwIfAborted(abortSignal, "build Docker template");
|
|
1890
|
+
const session = await openBuildSession(docker, secrets, abortSignal);
|
|
1783
1891
|
let stream;
|
|
1784
1892
|
try {
|
|
1785
1893
|
stream = await new Promise((resolve, reject) => {
|
|
1786
|
-
|
|
1894
|
+
let settled = false;
|
|
1895
|
+
let request;
|
|
1896
|
+
const finish = (error, data) => {
|
|
1897
|
+
if (settled) {
|
|
1898
|
+
if (data) data.destroy?.();
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
settled = true;
|
|
1902
|
+
abortSignal?.removeEventListener("abort", abort);
|
|
1903
|
+
error === void 0 ? resolve(data) : reject(error);
|
|
1904
|
+
};
|
|
1905
|
+
const abort = () => {
|
|
1906
|
+
context.destroy?.();
|
|
1907
|
+
request?.destroy?.();
|
|
1908
|
+
session.close();
|
|
1909
|
+
if (abortSignal) finish(createAbortError(abortSignal, "build Docker template"));
|
|
1910
|
+
};
|
|
1911
|
+
abortSignal?.addEventListener("abort", abort, { once: true });
|
|
1912
|
+
if (abortSignal?.aborted) {
|
|
1913
|
+
abort();
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
request = docker.modem.dial({
|
|
1787
1917
|
path: "/build?",
|
|
1788
1918
|
method: "POST",
|
|
1789
1919
|
file: context,
|
|
@@ -1798,7 +1928,7 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1798
1928
|
200: true,
|
|
1799
1929
|
500: "server error"
|
|
1800
1930
|
}
|
|
1801
|
-
}, (err, data) => err
|
|
1931
|
+
}, (err, data) => finish(err ?? void 0, data));
|
|
1802
1932
|
});
|
|
1803
1933
|
} catch (error) {
|
|
1804
1934
|
session.close();
|
|
@@ -1809,20 +1939,36 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1809
1939
|
session
|
|
1810
1940
|
};
|
|
1811
1941
|
}
|
|
1812
|
-
#followBuild(docker, stream) {
|
|
1942
|
+
#followBuild(docker, stream, abortSignal) {
|
|
1813
1943
|
return new Promise((resolve, reject) => {
|
|
1944
|
+
let settled = false;
|
|
1945
|
+
const finish = (error) => {
|
|
1946
|
+
if (settled) return;
|
|
1947
|
+
settled = true;
|
|
1948
|
+
abortSignal?.removeEventListener("abort", abort);
|
|
1949
|
+
error === void 0 ? resolve() : reject(error);
|
|
1950
|
+
};
|
|
1951
|
+
const abort = () => {
|
|
1952
|
+
stream.destroy?.();
|
|
1953
|
+
if (abortSignal) finish(createAbortError(abortSignal, "build Docker template"));
|
|
1954
|
+
};
|
|
1955
|
+
abortSignal?.addEventListener("abort", abort, { once: true });
|
|
1956
|
+
if (abortSignal?.aborted) {
|
|
1957
|
+
abort();
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1814
1960
|
docker.modem.followProgress(stream, (err, output) => {
|
|
1815
1961
|
if (err) {
|
|
1816
|
-
|
|
1962
|
+
finish(err);
|
|
1817
1963
|
return;
|
|
1818
1964
|
}
|
|
1819
1965
|
const failure = output?.find((entry) => entry && (entry.error !== void 0 || entry.errorDetail !== void 0));
|
|
1820
1966
|
if (failure) {
|
|
1821
1967
|
const detail = failure.errorDetail;
|
|
1822
|
-
|
|
1968
|
+
finish(new Error(String(detail?.message ?? failure.error ?? "docker build failed")));
|
|
1823
1969
|
return;
|
|
1824
1970
|
}
|
|
1825
|
-
|
|
1971
|
+
finish();
|
|
1826
1972
|
});
|
|
1827
1973
|
});
|
|
1828
1974
|
}
|
|
@@ -1971,16 +2117,31 @@ function createDockerRepoTemplate(options) {
|
|
|
1971
2117
|
if (options.ref !== void 0 && !REF_PATTERN.test(options.ref)) throw new Error(`Invalid ref '${options.ref}': expected a git ref name`);
|
|
1972
2118
|
const workingDirectory = trimTrailingSlashes(options.workingDirectory ?? DEFAULT_WORKING_DIRECTORY);
|
|
1973
2119
|
if (!workingDirectory.startsWith("/")) throw new Error(`workingDirectory must be an absolute path, got '${options.workingDirectory}'`);
|
|
1974
|
-
return () => resolveRepoTemplate(options, workingDirectory);
|
|
2120
|
+
return (resolveOptions) => resolveRepoTemplate(options, workingDirectory, resolveOptions);
|
|
1975
2121
|
}
|
|
1976
|
-
async function resolveRepoTemplate(options, workingDirectory) {
|
|
1977
|
-
const
|
|
2122
|
+
async function resolveRepoTemplate(options, workingDirectory, resolveOptions = {}) {
|
|
2123
|
+
const { abortSignal } = resolveOptions;
|
|
2124
|
+
throwIfAborted(abortSignal, "resolve Docker repository template");
|
|
2125
|
+
let access;
|
|
2126
|
+
try {
|
|
2127
|
+
access = await options.getRepositoryAccess(resolveOptions);
|
|
2128
|
+
} catch (error) {
|
|
2129
|
+
throw normalizeAbortError(error, "resolve Docker repository template");
|
|
2130
|
+
}
|
|
2131
|
+
throwIfAborted(abortSignal, "resolve Docker repository template");
|
|
1978
2132
|
const cloneUrl = access?.cloneUrl;
|
|
1979
2133
|
if (!cloneUrl) throw new Error("Repo template has no clone URL: repository access returned none.");
|
|
1980
2134
|
assertCloneUrl(cloneUrl);
|
|
1981
2135
|
const token = access?.authorization?.token;
|
|
1982
|
-
|
|
1983
|
-
|
|
2136
|
+
let buildEnv;
|
|
2137
|
+
try {
|
|
2138
|
+
buildEnv = typeof options.buildEnv === "function" ? await options.buildEnv(resolveOptions) : options.buildEnv;
|
|
2139
|
+
} catch (error) {
|
|
2140
|
+
throw normalizeAbortError(error, "resolve Docker repository template");
|
|
2141
|
+
}
|
|
2142
|
+
throwIfAborted(abortSignal, "resolve Docker repository template");
|
|
2143
|
+
const sha = await resolveHead(cloneUrl, options.ref, token, abortSignal);
|
|
2144
|
+
throwIfAborted(abortSignal, "resolve Docker repository template");
|
|
1984
2145
|
if (!sha) throw new Error(`Could not resolve ${options.ref ?? "HEAD"} of ${cloneUrl} with git ls-remote; check the ref, the credential and network access`);
|
|
1985
2146
|
return buildRepoTemplate({
|
|
1986
2147
|
cloneUrl,
|
|
@@ -2029,7 +2190,8 @@ function buildRepoTemplate(inputs) {
|
|
|
2029
2190
|
* yields undefined; the caller decides how to surface it.
|
|
2030
2191
|
* @internal exported for tests.
|
|
2031
2192
|
*/
|
|
2032
|
-
async function resolveHead(cloneUrl, ref, token) {
|
|
2193
|
+
async function resolveHead(cloneUrl, ref, token, abortSignal) {
|
|
2194
|
+
throwIfAborted(abortSignal, "resolve Docker repository template");
|
|
2033
2195
|
if (ref && FULL_SHA_PATTERN.test(ref)) return ref.toLowerCase();
|
|
2034
2196
|
try {
|
|
2035
2197
|
const authEnv = token ? {
|
|
@@ -2044,6 +2206,7 @@ async function resolveHead(cloneUrl, ref, token) {
|
|
|
2044
2206
|
ref ?? "HEAD"
|
|
2045
2207
|
], {
|
|
2046
2208
|
timeout: 1e4,
|
|
2209
|
+
signal: abortSignal,
|
|
2047
2210
|
env: {
|
|
2048
2211
|
...process.env,
|
|
2049
2212
|
...authEnv,
|
|
@@ -2053,7 +2216,8 @@ async function resolveHead(cloneUrl, ref, token) {
|
|
|
2053
2216
|
const lines = stdout.split("\n").filter(Boolean).map((line) => line.split(" "));
|
|
2054
2217
|
const sha = (lines.find(([, name]) => name?.endsWith("^{}")) ?? lines[0])?.[0]?.trim();
|
|
2055
2218
|
return sha && SHA_PATTERN.test(sha) ? sha.toLowerCase() : void 0;
|
|
2056
|
-
} catch {
|
|
2219
|
+
} catch (error) {
|
|
2220
|
+
if (abortSignal?.aborted) throw normalizeAbortError(error, "resolve Docker repository template");
|
|
2057
2221
|
return;
|
|
2058
2222
|
}
|
|
2059
2223
|
}
|