openship 0.6.9 → 0.7.1
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/bare-AB4SMTNJ.js +29 -0
- package/dist/{chunk-P7RCYELG.js → chunk-2BAKEGGF.js} +1 -1
- package/dist/{chunk-VAI5VOOI.js → chunk-2YAY5XHK.js} +1 -1
- package/dist/{chunk-X53JXRS4.js → chunk-37XZTLBJ.js} +3 -3
- package/dist/{chunk-R5DC5PEF.js → chunk-3K65MMYV.js} +39 -10
- package/dist/{chunk-D3OEMXYP.js → chunk-3WBKLDGC.js} +12 -7
- package/dist/{chunk-ENKJ7XIF.js → chunk-3YSSXSC2.js} +12 -12
- package/dist/{chunk-3MP6ACKM.js → chunk-7MCPDYKP.js} +799 -165
- package/dist/{chunk-D6HU4VUZ.js → chunk-7TQP6723.js} +2 -2
- package/dist/{chunk-SK4FFCFR.js → chunk-AMADTKC6.js} +1 -1
- package/dist/{chunk-NPSRGIHV.js → chunk-B2BQX6JX.js} +6 -5
- package/dist/{chunk-MUWVC5R4.js → chunk-BBPKAQ54.js} +12 -9
- package/dist/{chunk-WEZFTL4V.js → chunk-COI4VFRW.js} +217 -2
- package/dist/{chunk-5DJOB7IX.js → chunk-CQ2JOXEQ.js} +3 -3
- package/dist/{chunk-7PEXM2UI.js → chunk-DQR5TPFB.js} +6 -6
- package/dist/{chunk-IAMOUDHY.js → chunk-ETK6IBCH.js} +24 -15
- package/dist/{chunk-T46BQOLY.js → chunk-FNWNUYS3.js} +3 -3
- package/dist/{chunk-UAWY6X7V.js → chunk-GPTBEPUE.js} +485 -67
- package/dist/{chunk-634DDFT5.js → chunk-H36ASAYI.js} +8 -8
- package/dist/{chunk-AHJYP7DG.js → chunk-IVMPA6RC.js} +74 -17
- package/dist/{chunk-5UXHT6ED.js → chunk-IXBOV7BE.js} +113 -20
- package/dist/{chunk-GNOMEMNI.js → chunk-KATXZ5AB.js} +1 -1
- package/dist/{chunk-T6DVUX4F.js → chunk-KCMEWNP7.js} +14 -3
- package/dist/{chunk-N5YZMO33.js → chunk-L4DRH6ZA.js} +12 -1
- package/dist/{chunk-BO6CY4V2.js → chunk-NF7NQOAP.js} +47 -4
- package/dist/{chunk-FCGJN5Q5.js → chunk-NIOLZENU.js} +9 -9
- package/dist/{chunk-7YBWXEM6.js → chunk-OEYXXSAM.js} +4 -4
- package/dist/{chunk-6GU6JSK6.js → chunk-S5GU345P.js} +3 -3
- package/dist/{chunk-TAZZZKYO.js → chunk-TKK5NPDQ.js} +2 -2
- package/dist/{chunk-NUT6HKK6.js → chunk-WG5PDWZ3.js} +5 -5
- package/dist/{chunk-NIYD7MD2.js → chunk-XJSLJLNY.js} +90 -46
- package/dist/{cloud-REV3DPJH.js → cloud-T2JOFEQL.js} +20 -20
- package/dist/{detect-BBFT5GEU.js → detect-SWLZURYX.js} +5 -5
- package/dist/{docker-EG3QSE6I.js → docker-SQNZRZXN.js} +11 -11
- package/dist/{docker-edge-executor-E5TOTLMR.js → docker-edge-executor-BSSQQS76.js} +8 -8
- package/dist/{edge-import-YJVJUGYC.js → edge-import-LUDX77C2.js} +27 -27
- package/dist/{edge-preflight-QUW4BM7N.js → edge-preflight-JAEFWBRI.js} +14 -14
- package/dist/{ensure-container-edge-FOEFQ3PI.js → ensure-container-edge-U7FTLFVJ.js} +12 -12
- package/dist/{executor-NTWDJ3DB.js → executor-GYRZ7RIU.js} +7 -7
- package/dist/host-channel-preflight-U4IZCRXR.js +54 -0
- package/dist/index.js +72 -50
- package/dist/{local-executor-OQ2GRN7K.js → local-executor-JCR46VAP.js} +3 -3
- package/dist/{nginx-4U4C2XUS.js → nginx-YA6IRPJT.js} +10 -10
- package/dist/{openresty-lua-LLWT5XQW.js → openresty-lua-DUOB64TP.js} +8 -8
- package/dist/{privilege-MNDVMDST.js → privilege-4B2TMVW2.js} +6 -6
- package/dist/server/index.js +13286 -5381
- package/dist/server/lua/site_logger.lua +18 -1
- package/dist/server/migrations/0117_github_installation_workspace_scope.sql +24 -0
- package/dist/server/migrations/0118_git_sources.sql +66 -0
- package/dist/server/migrations/0119_backup_run_batch.sql +8 -0
- package/dist/server/migrations/0120_domain_primary_unique.sql +24 -0
- package/dist/server/migrations/0121_host_port_claim_overlap.sql +14 -0
- package/dist/server/migrations/0122_data_transfer_staging.sql +45 -0
- package/dist/server/migrations/meta/_journal.json +42 -0
- package/dist/setup-T3WN3HRS.js +25 -0
- package/dist/{ssh-client-2XWZCMGI.js → ssh-client-FZDWP5PQ.js} +3 -3
- package/dist/up-U73RUN5R.js +61 -0
- package/package.json +1 -1
- package/dist/bare-FA66N7GC.js +0 -29
- package/dist/host-channel-preflight-OI2TRHSG.js +0 -54
- package/dist/setup-IFIMPQZJ.js +0 -25
- package/dist/up-RC5ZQIAD.js +0 -61
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
prepareSourceTree,
|
|
10
10
|
resolveDockerBuildArgs,
|
|
11
11
|
resolveServiceDockerfile
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-S5GU345P.js";
|
|
13
13
|
import {
|
|
14
14
|
isArtifactPathRef,
|
|
15
15
|
removeManagedArtifact
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-AMADTKC6.js";
|
|
17
17
|
import {
|
|
18
18
|
BuildCancelledError,
|
|
19
19
|
BuildLogger,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
dockerBuildContextDirectory,
|
|
23
23
|
droppedRuntimeEnvMessage,
|
|
24
24
|
generateDockerfile,
|
|
25
|
+
gitShellCommand,
|
|
25
26
|
isExcludedDocRootEntry,
|
|
26
27
|
killProcessesUnderDir,
|
|
27
28
|
materializeGitSsh,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
sq,
|
|
35
36
|
staticBuilderOutputPath,
|
|
36
37
|
transferLocalDirectory
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-IVMPA6RC.js";
|
|
38
39
|
import {
|
|
39
40
|
DOCKER_DIAL_STDIO_COMMAND,
|
|
40
41
|
attachDialStdioDiagnostics,
|
|
@@ -43,17 +44,17 @@ import {
|
|
|
43
44
|
execSshCommand,
|
|
44
45
|
openDockerDialStdioChannel,
|
|
45
46
|
openSshUnixSocket
|
|
46
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-TKK5NPDQ.js";
|
|
47
48
|
import {
|
|
48
49
|
dirOf,
|
|
49
50
|
ensureOwnedDir
|
|
50
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-B2BQX6JX.js";
|
|
51
52
|
import {
|
|
52
53
|
OPENSHIP_LABEL
|
|
53
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-7TQP6723.js";
|
|
54
55
|
import {
|
|
55
56
|
isRuntimeNotFoundError
|
|
56
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-L4DRH6ZA.js";
|
|
57
58
|
import {
|
|
58
59
|
formatCpuCores,
|
|
59
60
|
formatMemoryMb,
|
|
@@ -64,7 +65,7 @@ import {
|
|
|
64
65
|
registryForImage,
|
|
65
66
|
safeErrorMessage,
|
|
66
67
|
withTimeout
|
|
67
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-COI4VFRW.js";
|
|
68
69
|
import {
|
|
69
70
|
__commonJS,
|
|
70
71
|
__require,
|
|
@@ -4626,10 +4627,46 @@ var require_tar_fs = __commonJS({
|
|
|
4626
4627
|
// ../../packages/adapters/src/runtime/docker.ts
|
|
4627
4628
|
var tarFs = __toESM(require_tar_fs(), 1);
|
|
4628
4629
|
import Dockerode from "dockerode";
|
|
4630
|
+
import { randomUUID } from "crypto";
|
|
4629
4631
|
import { createGzip } from "zlib";
|
|
4630
4632
|
import { PassThrough, Writable } from "stream";
|
|
4631
4633
|
import { relative, sep } from "path";
|
|
4632
4634
|
|
|
4635
|
+
// ../../packages/adapters/src/runtime/docker-container-info.ts
|
|
4636
|
+
function bindingPriority(ip) {
|
|
4637
|
+
const normalized = ip?.trim().replace(/^\[|\]$/g, "") ?? "";
|
|
4638
|
+
if (normalized === "127.0.0.1" || normalized === "::1") return 3;
|
|
4639
|
+
if (/^127(?:\.\d{1,3}){3}$/.test(normalized)) return 2;
|
|
4640
|
+
if (normalized && normalized !== "0.0.0.0" && normalized !== "::") return 1;
|
|
4641
|
+
return 0;
|
|
4642
|
+
}
|
|
4643
|
+
function dockerPublishedPortInfo(ports) {
|
|
4644
|
+
const selected = /* @__PURE__ */ new Map();
|
|
4645
|
+
for (const binding of ports) {
|
|
4646
|
+
if ((binding.type || "tcp").toLowerCase() !== "tcp") continue;
|
|
4647
|
+
if (!Number.isSafeInteger(binding.privatePort) || binding.privatePort < 1 || binding.privatePort > 65535) {
|
|
4648
|
+
continue;
|
|
4649
|
+
}
|
|
4650
|
+
const hostPort2 = binding.publicPort;
|
|
4651
|
+
if (typeof hostPort2 !== "number" || !Number.isSafeInteger(hostPort2) || hostPort2 < 1 || hostPort2 > 65535) {
|
|
4652
|
+
continue;
|
|
4653
|
+
}
|
|
4654
|
+
const priority = bindingPriority(binding.ip);
|
|
4655
|
+
const previous = selected.get(binding.privatePort);
|
|
4656
|
+
if (!previous || priority > previous.priority) {
|
|
4657
|
+
selected.set(binding.privatePort, { hostPort: hostPort2, priority });
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4660
|
+
const hostPortByContainerPort = Object.fromEntries(
|
|
4661
|
+
[...selected].map(([containerPort, binding]) => [containerPort, binding.hostPort])
|
|
4662
|
+
);
|
|
4663
|
+
const hostPort = selected.values().next().value?.hostPort;
|
|
4664
|
+
return {
|
|
4665
|
+
...hostPort !== void 0 ? { hostPort } : {},
|
|
4666
|
+
...selected.size > 0 ? { hostPortByContainerPort } : {}
|
|
4667
|
+
};
|
|
4668
|
+
}
|
|
4669
|
+
|
|
4633
4670
|
// ../../packages/adapters/src/runtime/docker-auth.ts
|
|
4634
4671
|
import { readFile } from "fs/promises";
|
|
4635
4672
|
import { homedir } from "os";
|
|
@@ -4805,11 +4842,13 @@ function bridgeSocket(socket, leftover) {
|
|
|
4805
4842
|
});
|
|
4806
4843
|
return out;
|
|
4807
4844
|
}
|
|
4808
|
-
function upgradeRequest(conn, path, body) {
|
|
4845
|
+
function upgradeRequest(conn, path, body, opts = {}) {
|
|
4809
4846
|
return new Promise((resolve, reject) => {
|
|
4810
4847
|
const socket = connect(conn);
|
|
4811
4848
|
let settled = false;
|
|
4812
4849
|
let head = Buffer.alloc(0);
|
|
4850
|
+
const protocol = opts.protocol ?? "tcp";
|
|
4851
|
+
const timeoutLabel = opts.timeoutLabel ?? "docker exec";
|
|
4813
4852
|
const fail = (err) => {
|
|
4814
4853
|
if (settled) return;
|
|
4815
4854
|
settled = true;
|
|
@@ -4818,7 +4857,7 @@ function upgradeRequest(conn, path, body) {
|
|
|
4818
4857
|
reject(err);
|
|
4819
4858
|
};
|
|
4820
4859
|
const timer = setTimeout(
|
|
4821
|
-
() => fail(new Error(
|
|
4860
|
+
() => fail(new Error(`${timeoutLabel} upgrade timed out after ${UPGRADE_TIMEOUT_MS}ms`)),
|
|
4822
4861
|
UPGRADE_TIMEOUT_MS
|
|
4823
4862
|
);
|
|
4824
4863
|
const onData = (chunk) => {
|
|
@@ -4827,7 +4866,8 @@ function upgradeRequest(conn, path, body) {
|
|
|
4827
4866
|
const text = head.toString("latin1");
|
|
4828
4867
|
const end = text.indexOf(HEADER_END);
|
|
4829
4868
|
if (end === -1) {
|
|
4830
|
-
if (head.length > 64 * 1024)
|
|
4869
|
+
if (head.length > 64 * 1024)
|
|
4870
|
+
fail(new Error(`${timeoutLabel} upgrade: response head too large`));
|
|
4831
4871
|
return;
|
|
4832
4872
|
}
|
|
4833
4873
|
const statusLine = text.slice(0, text.indexOf("\r\n"));
|
|
@@ -4848,14 +4888,17 @@ function upgradeRequest(conn, path, body) {
|
|
|
4848
4888
|
socket.on("data", onData);
|
|
4849
4889
|
socket.on("error", fail);
|
|
4850
4890
|
socket.on("close", onEarlyClose);
|
|
4891
|
+
const extraHeaders = Object.entries(opts.headers ?? {}).map(([name, value]) => `${name}: ${value}\r
|
|
4892
|
+
`).join("");
|
|
4893
|
+
const contentType = opts.contentType ? `Content-Type: ${opts.contentType}\r
|
|
4894
|
+
` : "";
|
|
4851
4895
|
socket.write(
|
|
4852
4896
|
`POST ${path} HTTP/1.1\r
|
|
4853
4897
|
Host: ${conn.socketPath ? "localhost" : conn.host ?? "localhost"}\r
|
|
4854
|
-
Content-
|
|
4855
|
-
Content-Length: ${Buffer.byteLength(body)}\r
|
|
4898
|
+
` + contentType + `Content-Length: ${Buffer.byteLength(body)}\r
|
|
4856
4899
|
Connection: Upgrade\r
|
|
4857
|
-
Upgrade:
|
|
4858
|
-
|
|
4900
|
+
Upgrade: ${protocol}\r
|
|
4901
|
+
` + extraHeaders + `\r
|
|
4859
4902
|
` + body
|
|
4860
4903
|
);
|
|
4861
4904
|
});
|
|
@@ -4864,7 +4907,8 @@ function startExecStream(conn, execId, opts) {
|
|
|
4864
4907
|
return upgradeRequest(
|
|
4865
4908
|
conn,
|
|
4866
4909
|
`/exec/${execId}/start`,
|
|
4867
|
-
JSON.stringify({ Detach: false, Tty: opts.tty })
|
|
4910
|
+
JSON.stringify({ Detach: false, Tty: opts.tty }),
|
|
4911
|
+
{ contentType: "application/json" }
|
|
4868
4912
|
);
|
|
4869
4913
|
}
|
|
4870
4914
|
function startAttachStream(conn, containerId, opts) {
|
|
@@ -4874,7 +4918,55 @@ function startAttachStream(conn, containerId, opts) {
|
|
|
4874
4918
|
stdout: opts.stdout ? "1" : "0",
|
|
4875
4919
|
stderr: opts.stderr ? "1" : "0"
|
|
4876
4920
|
});
|
|
4877
|
-
return upgradeRequest(conn, `/containers/${containerId}/attach?${q.toString()}`, ""
|
|
4921
|
+
return upgradeRequest(conn, `/containers/${containerId}/attach?${q.toString()}`, "", {
|
|
4922
|
+
contentType: "application/json"
|
|
4923
|
+
});
|
|
4924
|
+
}
|
|
4925
|
+
function startBuildKitSessionStream(conn, sessionId, sessionName) {
|
|
4926
|
+
if (!sessionId || /[\r\n]/.test(sessionId) || /[\r\n]/.test(sessionName)) {
|
|
4927
|
+
return Promise.reject(new Error("Invalid Docker BuildKit session identity"));
|
|
4928
|
+
}
|
|
4929
|
+
return upgradeRequest(conn, "/session", "", {
|
|
4930
|
+
protocol: "h2c",
|
|
4931
|
+
headers: {
|
|
4932
|
+
"X-Docker-Expose-Session-Uuid": sessionId,
|
|
4933
|
+
"X-Docker-Expose-Session-Name": sessionName
|
|
4934
|
+
},
|
|
4935
|
+
timeoutLabel: "Docker BuildKit session"
|
|
4936
|
+
});
|
|
4937
|
+
}
|
|
4938
|
+
var patchedBuildKitModems = /* @__PURE__ */ new WeakSet();
|
|
4939
|
+
function headerValue(headers, name) {
|
|
4940
|
+
if (!headers || typeof headers !== "object") return "";
|
|
4941
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
4942
|
+
if (key.toLowerCase() !== name.toLowerCase()) continue;
|
|
4943
|
+
if (Array.isArray(value)) return value.length > 0 ? String(value[0]) : "";
|
|
4944
|
+
return value == null ? "" : String(value);
|
|
4945
|
+
}
|
|
4946
|
+
return "";
|
|
4947
|
+
}
|
|
4948
|
+
function installDockerodeBuildKitSessionWorkaround(docker) {
|
|
4949
|
+
if (!docker.modem || typeof docker.modem !== "object") return;
|
|
4950
|
+
const modem = docker.modem;
|
|
4951
|
+
if (typeof modem.dial !== "function" || patchedBuildKitModems.has(modem)) return;
|
|
4952
|
+
const originalDial = modem.dial.bind(modem);
|
|
4953
|
+
modem.dial = (options, callback) => {
|
|
4954
|
+
const upgrade = headerValue(options.headers, "upgrade");
|
|
4955
|
+
if (options.path !== "/session" || options.hijack !== true || upgrade.toLowerCase() !== "h2c" || !callback) {
|
|
4956
|
+
return originalDial(options, callback);
|
|
4957
|
+
}
|
|
4958
|
+
const sessionId = headerValue(options.headers, "x-docker-expose-session-uuid");
|
|
4959
|
+
const sessionName = headerValue(options.headers, "x-docker-expose-session-name");
|
|
4960
|
+
void startBuildKitSessionStream(
|
|
4961
|
+
daemonConnectionFrom(docker),
|
|
4962
|
+
sessionId,
|
|
4963
|
+
sessionName || "openship"
|
|
4964
|
+
).then(
|
|
4965
|
+
(socket) => callback(null, socket),
|
|
4966
|
+
(error) => callback(error instanceof Error ? error : new Error(String(error)), null)
|
|
4967
|
+
);
|
|
4968
|
+
};
|
|
4969
|
+
patchedBuildKitModems.add(modem);
|
|
4878
4970
|
}
|
|
4879
4971
|
function daemonConnectionFrom(docker) {
|
|
4880
4972
|
const m = docker.modem ?? {};
|
|
@@ -5113,7 +5205,10 @@ function awaitFirstByte(upstream, ms) {
|
|
|
5113
5205
|
upstream.removeListener("close", onFail);
|
|
5114
5206
|
resolve(outcome);
|
|
5115
5207
|
};
|
|
5116
|
-
const onData = (chunk) =>
|
|
5208
|
+
const onData = (chunk) => {
|
|
5209
|
+
upstream.pause();
|
|
5210
|
+
finish({ kind: "data", chunk });
|
|
5211
|
+
};
|
|
5117
5212
|
const onFail = () => finish({ kind: "closed" });
|
|
5118
5213
|
const timer = setTimeout(() => finish({ kind: "silent" }), ms);
|
|
5119
5214
|
upstream.on("data", onData);
|
|
@@ -5373,7 +5468,9 @@ function createDockerSshBridge(opts) {
|
|
|
5373
5468
|
firstByte.kind === "closed" ? "channel closed before answering" : "channel opened but no data flowed"
|
|
5374
5469
|
);
|
|
5375
5470
|
} catch (err) {
|
|
5376
|
-
console.warn(
|
|
5471
|
+
console.warn(
|
|
5472
|
+
`[docker-ssh] bridge client failed (${opts.host ?? "?"}): ${safeErrorMessage(err)}`
|
|
5473
|
+
);
|
|
5377
5474
|
capture.fail(bridgeFailureReason(opts, safeErrorMessage(err)));
|
|
5378
5475
|
}
|
|
5379
5476
|
};
|
|
@@ -5386,7 +5483,9 @@ function createDockerSshBridge(opts) {
|
|
|
5386
5483
|
void bridgeClient(client);
|
|
5387
5484
|
});
|
|
5388
5485
|
server.on("error", (err) => {
|
|
5389
|
-
console.warn(
|
|
5486
|
+
console.warn(
|
|
5487
|
+
`[docker-ssh] bridge listener error (${opts.host ?? "?"}): ${safeErrorMessage(err)}`
|
|
5488
|
+
);
|
|
5390
5489
|
});
|
|
5391
5490
|
return {
|
|
5392
5491
|
start: () => new Promise((resolve, reject) => {
|
|
@@ -5532,6 +5631,244 @@ function resolveDockerTransport(opts) {
|
|
|
5532
5631
|
};
|
|
5533
5632
|
}
|
|
5534
5633
|
|
|
5634
|
+
// ../../packages/adapters/src/runtime/docker-build-diagnostics.ts
|
|
5635
|
+
var DEFAULT_DOCKER_BUILD_IDLE_TIMEOUT_MS = 10 * 6e4;
|
|
5636
|
+
var MIN_DOCKER_BUILD_IDLE_TIMEOUT_MS = 6e4;
|
|
5637
|
+
var MAX_DOCKER_BUILD_IDLE_TIMEOUT_MS = 24 * 60 * 6e4;
|
|
5638
|
+
function getDockerBuildIdleTimeoutMs(raw = process.env.OPENSHIP_BUILD_IDLE_TIMEOUT_MS) {
|
|
5639
|
+
if (raw == null || raw.trim() === "") return DEFAULT_DOCKER_BUILD_IDLE_TIMEOUT_MS;
|
|
5640
|
+
const value = Number(raw);
|
|
5641
|
+
return Number.isSafeInteger(value) && value >= MIN_DOCKER_BUILD_IDLE_TIMEOUT_MS && value <= MAX_DOCKER_BUILD_IDLE_TIMEOUT_MS ? value : DEFAULT_DOCKER_BUILD_IDLE_TIMEOUT_MS;
|
|
5642
|
+
}
|
|
5643
|
+
function memoryGuidance(context) {
|
|
5644
|
+
const memoryMb = context.configuredMemoryMb;
|
|
5645
|
+
if (context.memoryLimitApplied && memoryMb && memoryMb > 0) {
|
|
5646
|
+
return `The classic Docker builder was capped at ${memoryMb} MB RAM; raise Build Memory in Project Settings \u2192 Resources if this build needs more.`;
|
|
5647
|
+
}
|
|
5648
|
+
return "This build path was not under an OpenShip-enforced memory cap; check available RAM/swap and the host or Docker daemon OOM logs.";
|
|
5649
|
+
}
|
|
5650
|
+
function exitCodeFromLine(line) {
|
|
5651
|
+
const match = line.match(
|
|
5652
|
+
/(?:returned a non-zero code|exited? with(?: exit)? code|exit code)\s*:?\s*(\d+)\b/i
|
|
5653
|
+
);
|
|
5654
|
+
return match ? Number(match[1]) : null;
|
|
5655
|
+
}
|
|
5656
|
+
function extractDockerBuildFailureHint(line, context = {}) {
|
|
5657
|
+
const code = exitCodeFromLine(line);
|
|
5658
|
+
if (code === 137) {
|
|
5659
|
+
return `${line} \u2014 The build process was killed by SIGKILL (exit code 137). Memory exhaustion is the most common cause, but exit 137 alone does not prove OOM. ${memoryGuidance(context)}`;
|
|
5660
|
+
}
|
|
5661
|
+
if (code === 143) {
|
|
5662
|
+
return `${line} \u2014 The build process received SIGTERM (exit code 143), usually because it was cancelled or stopped by another supervisor.`;
|
|
5663
|
+
}
|
|
5664
|
+
if (code !== null) return line;
|
|
5665
|
+
if (/JavaScript heap out of memory|Fatal process out of memory|Ineffective mark-compacts near heap limit|Reached heap limit Allocation failed|fatal error:\s*(?:runtime:\s*)?out of memory/i.test(
|
|
5666
|
+
line
|
|
5667
|
+
)) {
|
|
5668
|
+
return `${line} \u2014 The build process explicitly reported that it ran out of memory. ${memoryGuidance(context)}`;
|
|
5669
|
+
}
|
|
5670
|
+
if (/\bENOMEM\b|cannot allocate memory/i.test(line)) {
|
|
5671
|
+
return `${line} \u2014 The build process could not allocate memory (ENOMEM). ${memoryGuidance(context)}`;
|
|
5672
|
+
}
|
|
5673
|
+
return null;
|
|
5674
|
+
}
|
|
5675
|
+
function chooseDockerBuildFailureHint(current, candidate) {
|
|
5676
|
+
if (!candidate) return current;
|
|
5677
|
+
if (!current) return candidate;
|
|
5678
|
+
const score = (message) => {
|
|
5679
|
+
if (/explicitly reported that it ran out of memory|could not allocate memory/i.test(message)) {
|
|
5680
|
+
return 4;
|
|
5681
|
+
}
|
|
5682
|
+
if (/SIGKILL \(exit code 137\)/i.test(message)) return 3;
|
|
5683
|
+
if (/BuildKit|package\.json was not found/i.test(message)) return 2;
|
|
5684
|
+
if (/returned a non-zero code|failed to solve|executor failed running|error: build/i.test(message)) {
|
|
5685
|
+
return 1;
|
|
5686
|
+
}
|
|
5687
|
+
return 0;
|
|
5688
|
+
};
|
|
5689
|
+
return score(candidate) > score(current) ? candidate : current;
|
|
5690
|
+
}
|
|
5691
|
+
function dockerBuildExitMessage(code, streamedHint, context = {}) {
|
|
5692
|
+
if (streamedHint) return streamedHint;
|
|
5693
|
+
const plain = `docker build exited with code ${code}`;
|
|
5694
|
+
return extractDockerBuildFailureHint(plain, context) ?? plain;
|
|
5695
|
+
}
|
|
5696
|
+
function formatDuration(ms) {
|
|
5697
|
+
if (ms % 6e4 === 0) {
|
|
5698
|
+
const minutes = ms / 6e4;
|
|
5699
|
+
return `${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
5700
|
+
}
|
|
5701
|
+
const seconds = Math.ceil(ms / 1e3);
|
|
5702
|
+
return `${seconds} second${seconds === 1 ? "" : "s"}`;
|
|
5703
|
+
}
|
|
5704
|
+
function dockerBuildIdleTimeoutError(timeoutMs, context = {}) {
|
|
5705
|
+
const limit = context.memoryLimitApplied && context.configuredMemoryMb && context.configuredMemoryMb > 0 ? ` The classic Docker builder was capped at ${context.configuredMemoryMb} MB RAM, so sustained memory pressure is one possible cause.` : "";
|
|
5706
|
+
return new Error(
|
|
5707
|
+
`Docker build produced no output for ${formatDuration(timeoutMs)} and was cancelled.${limit} Other common causes are package-registry or DNS failures, a process waiting for input, and a stalled Docker daemon.`
|
|
5708
|
+
);
|
|
5709
|
+
}
|
|
5710
|
+
function startDockerBuildIdleMonitor(options) {
|
|
5711
|
+
let stopped = false;
|
|
5712
|
+
let lastProgressAt = Date.now();
|
|
5713
|
+
let lastReportedMinute = 0;
|
|
5714
|
+
let deadline;
|
|
5715
|
+
const armDeadline = () => {
|
|
5716
|
+
clearTimeout(deadline);
|
|
5717
|
+
deadline = setTimeout(() => {
|
|
5718
|
+
if (stopped) return;
|
|
5719
|
+
stopped = true;
|
|
5720
|
+
clearInterval(heartbeat);
|
|
5721
|
+
options.onTimeout();
|
|
5722
|
+
}, options.timeoutMs);
|
|
5723
|
+
deadline.unref?.();
|
|
5724
|
+
};
|
|
5725
|
+
const heartbeat = setInterval(() => {
|
|
5726
|
+
if (stopped) return;
|
|
5727
|
+
const idleMs = Date.now() - lastProgressAt;
|
|
5728
|
+
const idleMinutes = Math.floor(idleMs / 6e4);
|
|
5729
|
+
if (idleMinutes > lastReportedMinute) {
|
|
5730
|
+
lastReportedMinute = idleMinutes;
|
|
5731
|
+
options.onIdle(idleMs);
|
|
5732
|
+
}
|
|
5733
|
+
}, 6e4);
|
|
5734
|
+
heartbeat.unref?.();
|
|
5735
|
+
armDeadline();
|
|
5736
|
+
return {
|
|
5737
|
+
progress() {
|
|
5738
|
+
if (stopped) return;
|
|
5739
|
+
lastProgressAt = Date.now();
|
|
5740
|
+
lastReportedMinute = 0;
|
|
5741
|
+
armDeadline();
|
|
5742
|
+
},
|
|
5743
|
+
stop() {
|
|
5744
|
+
if (stopped) return;
|
|
5745
|
+
stopped = true;
|
|
5746
|
+
clearTimeout(deadline);
|
|
5747
|
+
clearInterval(heartbeat);
|
|
5748
|
+
}
|
|
5749
|
+
};
|
|
5750
|
+
}
|
|
5751
|
+
|
|
5752
|
+
// ../../packages/adapters/src/runtime/docker-build-container-tracker.ts
|
|
5753
|
+
function normalizedImageId(value) {
|
|
5754
|
+
return (value ?? "").replace(/^sha256:/, "").toLowerCase();
|
|
5755
|
+
}
|
|
5756
|
+
function commandMatches(instruction, actual) {
|
|
5757
|
+
const run = instruction.match(/^RUN\s+([\s\S]+)$/i)?.[1]?.trim();
|
|
5758
|
+
if (!run || !actual?.length) return false;
|
|
5759
|
+
if (run.startsWith("[")) {
|
|
5760
|
+
try {
|
|
5761
|
+
const parsed = JSON.parse(run);
|
|
5762
|
+
return Array.isArray(parsed) && parsed.every((part) => typeof part === "string") && parsed.length === actual.length && parsed.every((part, index) => part === actual[index]);
|
|
5763
|
+
} catch {
|
|
5764
|
+
return false;
|
|
5765
|
+
}
|
|
5766
|
+
}
|
|
5767
|
+
return actual[actual.length - 1] === run;
|
|
5768
|
+
}
|
|
5769
|
+
var LegacyBuildContainerTracker = class {
|
|
5770
|
+
constructor(client, expectedMemoryBytes, ownershipHost) {
|
|
5771
|
+
this.client = client;
|
|
5772
|
+
this.expectedMemoryBytes = expectedMemoryBytes;
|
|
5773
|
+
this.ownershipHost = ownershipHost;
|
|
5774
|
+
}
|
|
5775
|
+
parentImageId = null;
|
|
5776
|
+
pendingStep = null;
|
|
5777
|
+
active = null;
|
|
5778
|
+
expectedStepTotal = null;
|
|
5779
|
+
lastStepNumber = 0;
|
|
5780
|
+
chunkBuffer = "";
|
|
5781
|
+
queue = Promise.resolve();
|
|
5782
|
+
termination = null;
|
|
5783
|
+
/** Consume one complete dockerode `event.stream` field. */
|
|
5784
|
+
observe(stream) {
|
|
5785
|
+
if (!stream) return;
|
|
5786
|
+
for (const rawLine of stream.split(/\r?\n/)) {
|
|
5787
|
+
this.enqueueLine(rawLine);
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5790
|
+
/** Consume an arbitrary SSH stdout/stderr chunk, retaining a partial line. */
|
|
5791
|
+
observeChunk(chunk) {
|
|
5792
|
+
if (!chunk) return;
|
|
5793
|
+
const parts = `${this.chunkBuffer}${chunk}`.split(/\r\n|\n|\r/);
|
|
5794
|
+
this.chunkBuffer = parts.pop() ?? "";
|
|
5795
|
+
if (this.chunkBuffer.length > 8192) this.chunkBuffer = this.chunkBuffer.slice(-8192);
|
|
5796
|
+
for (const line of parts) this.enqueueLine(line);
|
|
5797
|
+
}
|
|
5798
|
+
flush() {
|
|
5799
|
+
this.enqueueLine(this.chunkBuffer);
|
|
5800
|
+
this.chunkBuffer = "";
|
|
5801
|
+
}
|
|
5802
|
+
enqueueLine(rawLine) {
|
|
5803
|
+
const line = rawLine.trim();
|
|
5804
|
+
if (!line) return;
|
|
5805
|
+
this.queue = this.queue.then(
|
|
5806
|
+
() => this.observeLine(line),
|
|
5807
|
+
() => this.observeLine(line)
|
|
5808
|
+
);
|
|
5809
|
+
}
|
|
5810
|
+
async observeLine(line) {
|
|
5811
|
+
if (this.active) {
|
|
5812
|
+
const removed = line.match(/^---> Removed intermediate container\s+([a-f0-9]{12,64})$/i);
|
|
5813
|
+
if (removed?.[1]?.toLowerCase() === this.active.id) {
|
|
5814
|
+
try {
|
|
5815
|
+
const info = await this.client.inspect(this.active.id);
|
|
5816
|
+
if (!info?.State?.Running) this.active = null;
|
|
5817
|
+
} catch {
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
return;
|
|
5821
|
+
}
|
|
5822
|
+
const layer = line.match(/^--->\s+([a-f0-9]{12,64})$/i);
|
|
5823
|
+
if (layer?.[1]) {
|
|
5824
|
+
this.parentImageId = layer[1].toLowerCase();
|
|
5825
|
+
return;
|
|
5826
|
+
}
|
|
5827
|
+
const step = line.match(/^Step\s+(\d+)\/(\d+)\s*:\s*([\s\S]+)$/i);
|
|
5828
|
+
if (step?.[1] && step[2] && step[3]) {
|
|
5829
|
+
const number = Number(step[1]);
|
|
5830
|
+
const total = Number(step[2]);
|
|
5831
|
+
const firstStep = this.lastStepNumber === 0 && number === 1;
|
|
5832
|
+
const nextStep = number === this.lastStepNumber + 1;
|
|
5833
|
+
const sameTotal = this.expectedStepTotal === null || total === this.expectedStepTotal;
|
|
5834
|
+
if (number >= 1 && total >= number && sameTotal && (firstStep || nextStep)) {
|
|
5835
|
+
this.expectedStepTotal ??= total;
|
|
5836
|
+
this.lastStepNumber = number;
|
|
5837
|
+
this.pendingStep = {
|
|
5838
|
+
number,
|
|
5839
|
+
total,
|
|
5840
|
+
instruction: step[3].trim(),
|
|
5841
|
+
parentImageId: this.parentImageId
|
|
5842
|
+
};
|
|
5843
|
+
}
|
|
5844
|
+
return;
|
|
5845
|
+
}
|
|
5846
|
+
const running = line.match(/^---> Running in\s+([a-f0-9]{12,64})$/i);
|
|
5847
|
+
if (running?.[1] && this.pendingStep && /^RUN\s+/i.test(this.pendingStep.instruction)) {
|
|
5848
|
+
this.active = { ...this.pendingStep, id: running[1].toLowerCase() };
|
|
5849
|
+
this.pendingStep = null;
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
async terminate() {
|
|
5853
|
+
await this.queue;
|
|
5854
|
+
const candidate = this.active;
|
|
5855
|
+
if (!candidate) return { status: "none" };
|
|
5856
|
+
const info = await this.client.inspect(candidate.id);
|
|
5857
|
+
if (!info?.State?.Running) return { status: "not-running", id: candidate.id };
|
|
5858
|
+
const actualId = info.Id?.toLowerCase() ?? "";
|
|
5859
|
+
const actualImage = normalizedImageId(info.Config?.Image);
|
|
5860
|
+
const expectedMemory = this.expectedMemoryBytes ?? 0;
|
|
5861
|
+
const ownershipEntry = `${this.ownershipHost}:127.0.0.1`;
|
|
5862
|
+
const verified = actualId.startsWith(candidate.id) && candidate.parentImageId !== null && actualImage.startsWith(candidate.parentImageId) && (info.HostConfig?.Memory ?? 0) === expectedMemory && info.HostConfig?.ExtraHosts?.includes(ownershipEntry) === true && commandMatches(candidate.instruction, info.Config?.Cmd);
|
|
5863
|
+
if (!verified) return { status: "unverified", id: candidate.id };
|
|
5864
|
+
await this.client.kill(candidate.id);
|
|
5865
|
+
return { status: "killed", id: candidate.id };
|
|
5866
|
+
}
|
|
5867
|
+
terminateActive() {
|
|
5868
|
+
return this.termination ??= this.terminate();
|
|
5869
|
+
}
|
|
5870
|
+
};
|
|
5871
|
+
|
|
5535
5872
|
// ../../packages/adapters/src/runtime/docker.ts
|
|
5536
5873
|
var isDockerNotFoundError = isRuntimeNotFoundError;
|
|
5537
5874
|
function ownsBuiltImage(imageRef) {
|
|
@@ -5549,7 +5886,42 @@ var RESTART_POLICIES = {
|
|
|
5549
5886
|
"unless-stopped": { Name: "unless-stopped", MaximumRetryCount: 0 },
|
|
5550
5887
|
no: { Name: "no", MaximumRetryCount: 0 }
|
|
5551
5888
|
};
|
|
5552
|
-
var
|
|
5889
|
+
var BUILD_CONTAINER_CLEANUP_TIMEOUT_MS = 5e3;
|
|
5890
|
+
var BUILD_CONTAINER_IDENTIFICATION_GRACE_MS = 500;
|
|
5891
|
+
function newBuildOwnershipHost() {
|
|
5892
|
+
return `openship-build-${randomUUID()}.invalid`;
|
|
5893
|
+
}
|
|
5894
|
+
async function boundedBuildContainerCleanup(cleanup) {
|
|
5895
|
+
let timer = null;
|
|
5896
|
+
const guarded = cleanup.catch(() => null);
|
|
5897
|
+
const deadline = new Promise((resolve) => {
|
|
5898
|
+
timer = setTimeout(() => resolve(null), BUILD_CONTAINER_CLEANUP_TIMEOUT_MS);
|
|
5899
|
+
timer.unref?.();
|
|
5900
|
+
});
|
|
5901
|
+
try {
|
|
5902
|
+
return await Promise.race([guarded, deadline]);
|
|
5903
|
+
} finally {
|
|
5904
|
+
if (timer) clearTimeout(timer);
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5907
|
+
async function stopTrackedBuildContainer(tracker, log, reason) {
|
|
5908
|
+
if (!tracker) return;
|
|
5909
|
+
tracker.flush();
|
|
5910
|
+
const cleanup = await boundedBuildContainerCleanup(tracker.terminateActive());
|
|
5911
|
+
if (cleanup?.status === "killed") {
|
|
5912
|
+
log.log(`Stopped ${reason} Docker build container ${cleanup.id.slice(0, 12)}.`);
|
|
5913
|
+
} else if (cleanup?.status === "unverified") {
|
|
5914
|
+
log.log(
|
|
5915
|
+
`${reason === "timed-out" ? "Timed-out" : "Interrupted"} build container ${cleanup.id.slice(0, 12)} did not match this build's verified step; it was left untouched.`,
|
|
5916
|
+
"warn"
|
|
5917
|
+
);
|
|
5918
|
+
} else if (cleanup === null) {
|
|
5919
|
+
log.log(
|
|
5920
|
+
`${reason === "timed-out" ? "Timed-out" : "Interrupted"} Docker build cleanup could not be confirmed within 5 seconds; Docker may still be releasing the build container.`,
|
|
5921
|
+
"warn"
|
|
5922
|
+
);
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5553
5925
|
var activeDockerBuilds = /* @__PURE__ */ new Map();
|
|
5554
5926
|
var pendingDockerBuildCancellations = /* @__PURE__ */ new Map();
|
|
5555
5927
|
var PENDING_CANCEL_TTL_MS = 10 * 60 * 1e3;
|
|
@@ -5719,21 +6091,34 @@ function parsePortBindings(portSpecs) {
|
|
|
5719
6091
|
}
|
|
5720
6092
|
function packBuildContext(contextDir, entries, cancelSignal, ignoreContextPath) {
|
|
5721
6093
|
const controller = new AbortController();
|
|
6094
|
+
const abort = (reason) => {
|
|
6095
|
+
if (!controller.signal.aborted) controller.abort(reason);
|
|
6096
|
+
};
|
|
5722
6097
|
const ignore = ignoreContextPath ? (absolutePath) => ignoreContextPath(relative(contextDir, absolutePath).split(sep).join("/")) : void 0;
|
|
5723
6098
|
const pack2 = tarFs.pack(contextDir, { entries, ignore });
|
|
5724
6099
|
const body = pack2.pipe(createGzip());
|
|
5725
6100
|
let contextError = null;
|
|
5726
6101
|
const capture = (err) => {
|
|
5727
6102
|
contextError ??= err instanceof Error ? err : new Error(String(err));
|
|
5728
|
-
|
|
6103
|
+
abort(contextError);
|
|
5729
6104
|
};
|
|
6105
|
+
const onCancel = () => abort(cancelSignal?.reason);
|
|
5730
6106
|
if (cancelSignal) {
|
|
5731
|
-
if (cancelSignal.aborted)
|
|
5732
|
-
else cancelSignal.addEventListener("abort",
|
|
6107
|
+
if (cancelSignal.aborted) abort(cancelSignal.reason);
|
|
6108
|
+
else cancelSignal.addEventListener("abort", onCancel, { once: true });
|
|
5733
6109
|
}
|
|
5734
6110
|
pack2.once("error", capture);
|
|
5735
6111
|
body.once("error", capture);
|
|
5736
|
-
return {
|
|
6112
|
+
return {
|
|
6113
|
+
body,
|
|
6114
|
+
abortSignal: controller.signal,
|
|
6115
|
+
abort,
|
|
6116
|
+
// Once buildImage has returned the response stream, streamDockerodeBuild
|
|
6117
|
+
// owns cancellation. It briefly keeps that stream alive to receive Docker's
|
|
6118
|
+
// exact intermediate-container id before aborting the request.
|
|
6119
|
+
detachCancellation: () => cancelSignal?.removeEventListener("abort", onCancel),
|
|
6120
|
+
takeError: () => contextError
|
|
6121
|
+
};
|
|
5737
6122
|
}
|
|
5738
6123
|
var DURATION_UNITS_NS = {
|
|
5739
6124
|
ns: 1,
|
|
@@ -5852,38 +6237,28 @@ function extractNetworkInfo(data) {
|
|
|
5852
6237
|
break;
|
|
5853
6238
|
}
|
|
5854
6239
|
}
|
|
5855
|
-
const hostPortByContainerPort = {};
|
|
5856
|
-
let hostPort;
|
|
5857
6240
|
const liveBindings = data.NetworkSettings?.Ports ?? {};
|
|
5858
6241
|
const configuredBindings = data.HostConfig?.PortBindings ?? {};
|
|
6242
|
+
const publishedPorts = [];
|
|
5859
6243
|
for (const key of /* @__PURE__ */ new Set([...Object.keys(liveBindings), ...Object.keys(configuredBindings)])) {
|
|
5860
6244
|
const [containerRaw, protocol = "tcp"] = key.split("/");
|
|
5861
|
-
if (protocol.toLowerCase() !== "tcp") continue;
|
|
5862
6245
|
const containerPort = Number(containerRaw);
|
|
5863
|
-
if (!Number.isSafeInteger(containerPort) || containerPort < 1 || containerPort > 65535) {
|
|
5864
|
-
continue;
|
|
5865
|
-
}
|
|
5866
6246
|
const live = liveBindings[key];
|
|
5867
6247
|
const bindings = live && live.some((binding) => binding?.HostPort) ? live : configuredBindings[key];
|
|
5868
|
-
const
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
(candidate) => Boolean(candidate)
|
|
5876
|
-
).sort((left, right) => right.priority - left.priority || left.index - right.index);
|
|
5877
|
-
const selected = candidates[0]?.port;
|
|
5878
|
-
if (selected !== void 0) {
|
|
5879
|
-
hostPortByContainerPort[containerPort] = selected;
|
|
5880
|
-
hostPort ??= selected;
|
|
6248
|
+
for (const binding of bindings ?? []) {
|
|
6249
|
+
publishedPorts.push({
|
|
6250
|
+
privatePort: containerPort,
|
|
6251
|
+
publicPort: Number(binding?.HostPort),
|
|
6252
|
+
type: protocol,
|
|
6253
|
+
...binding?.HostIp ? { ip: binding.HostIp } : {}
|
|
6254
|
+
});
|
|
5881
6255
|
}
|
|
5882
6256
|
}
|
|
6257
|
+
const { hostPort, hostPortByContainerPort } = dockerPublishedPortInfo(publishedPorts);
|
|
5883
6258
|
return {
|
|
5884
6259
|
ip,
|
|
5885
6260
|
hostPort,
|
|
5886
|
-
...
|
|
6261
|
+
...hostPortByContainerPort ? { hostPortByContainerPort } : {}
|
|
5887
6262
|
};
|
|
5888
6263
|
}
|
|
5889
6264
|
function parseLoadedImageRef(output) {
|
|
@@ -5988,6 +6363,9 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
5988
6363
|
static async create(opts, systemManager, provisionLock) {
|
|
5989
6364
|
const runtime = new _DockerRuntime(opts, systemManager, provisionLock);
|
|
5990
6365
|
runtime._docker = new Dockerode(await runtime.transport.establish());
|
|
6366
|
+
if (process.versions.bun) {
|
|
6367
|
+
installDockerodeBuildKitSessionWorkaround(runtime._docker);
|
|
6368
|
+
}
|
|
5991
6369
|
return runtime;
|
|
5992
6370
|
}
|
|
5993
6371
|
supports(cap) {
|
|
@@ -6088,17 +6466,20 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6088
6466
|
logger.log(`[${step}] ${label} ${message}`);
|
|
6089
6467
|
logger.step(step, status, message);
|
|
6090
6468
|
}
|
|
6091
|
-
handleBuildEvent(event, logger, trace) {
|
|
6469
|
+
handleBuildEvent(event, logger, trace, diagnosticContext = {}) {
|
|
6092
6470
|
const errorMessage = event.errorDetail?.message ?? event.error;
|
|
6093
6471
|
if (errorMessage) {
|
|
6094
6472
|
logger.log(errorMessage, "error");
|
|
6095
|
-
return errorMessage;
|
|
6473
|
+
return this.extractBuildFailureHint(errorMessage, diagnosticContext) ?? errorMessage;
|
|
6096
6474
|
}
|
|
6097
6475
|
if (event.id === "moby.buildkit.trace" && typeof event.aux === "string") {
|
|
6098
6476
|
let hint = null;
|
|
6099
6477
|
for (const line of trace?.push(event.aux) ?? []) {
|
|
6100
6478
|
logger.log(line.message, line.level === "error" ? "error" : parseLogLevel(line.message));
|
|
6101
|
-
hint
|
|
6479
|
+
hint = chooseDockerBuildFailureHint(
|
|
6480
|
+
hint,
|
|
6481
|
+
this.extractBuildFailureHint(line.message, diagnosticContext)
|
|
6482
|
+
);
|
|
6102
6483
|
}
|
|
6103
6484
|
return hint;
|
|
6104
6485
|
}
|
|
@@ -6125,7 +6506,7 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6125
6506
|
return null;
|
|
6126
6507
|
}
|
|
6127
6508
|
logger.log(line, parseLogLevel(line));
|
|
6128
|
-
return this.extractBuildFailureHint(line);
|
|
6509
|
+
return this.extractBuildFailureHint(line, diagnosticContext);
|
|
6129
6510
|
}
|
|
6130
6511
|
if (event.status) {
|
|
6131
6512
|
const parts = [event.id, event.status, event.progress].filter((p) => Boolean(p?.trim())).map((p) => p.trim());
|
|
@@ -6154,10 +6535,9 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6154
6535
|
isLowSignalDockerLine(line) {
|
|
6155
6536
|
return _DockerRuntime.DOCKER_BUILDER_NOISE.some((p) => p.test(line));
|
|
6156
6537
|
}
|
|
6157
|
-
extractBuildFailureHint(line) {
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
}
|
|
6538
|
+
extractBuildFailureHint(line, diagnosticContext = {}) {
|
|
6539
|
+
const processFailure = extractDockerBuildFailureHint(line, diagnosticContext);
|
|
6540
|
+
if (processFailure) return processFailure;
|
|
6161
6541
|
if (/requires? BuildKit|BuildKit is enabled but the buildx component/i.test(line)) {
|
|
6162
6542
|
return `${line} \u2014 this Dockerfile needs BuildKit. Install the docker buildx plugin on the build host (\`docker-buildx-plugin\`), or remove the BuildKit-only instruction.`;
|
|
6163
6543
|
}
|
|
@@ -6254,7 +6634,8 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6254
6634
|
const dockerfileFlag = dockerfileName && dockerfileName !== "Dockerfile" ? ` -f ${sq(dockerfileName)}` : "";
|
|
6255
6635
|
const buildKit = await this.remoteBuildKitAvailable();
|
|
6256
6636
|
const builderEnv = buildKit ? "DOCKER_BUILDKIT=1 " : "";
|
|
6257
|
-
const
|
|
6637
|
+
const ownershipHost = buildKit ? null : newBuildOwnershipHost();
|
|
6638
|
+
const builderFlags = buildKit ? " --progress=plain" : ` --force-rm --add-host ${sq(`${ownershipHost}:127.0.0.1`)}`;
|
|
6258
6639
|
if (buildKit) {
|
|
6259
6640
|
log.log("Builder: BuildKit (docker buildx detected on the host)");
|
|
6260
6641
|
} else if (opts?.requiresBuildKit) {
|
|
@@ -6278,16 +6659,79 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6278
6659
|
"running",
|
|
6279
6660
|
"Running install inside container (docker build)"
|
|
6280
6661
|
);
|
|
6281
|
-
const
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6662
|
+
const diagnosticContext = {
|
|
6663
|
+
configuredMemoryMb: config.resources.memoryMb,
|
|
6664
|
+
memoryLimitApplied: false
|
|
6665
|
+
};
|
|
6666
|
+
const buildContainerTracker = buildKit ? null : this.remoteLegacyBuildContainerTracker(executor, ownershipHost);
|
|
6667
|
+
const commandAbort = new AbortController();
|
|
6668
|
+
const forwardCancellation = () => {
|
|
6669
|
+
buildContainerTracker?.flush();
|
|
6670
|
+
if (buildContainerTracker) void buildContainerTracker.terminateActive();
|
|
6671
|
+
commandAbort.abort(signal?.reason);
|
|
6672
|
+
};
|
|
6673
|
+
if (signal?.aborted) forwardCancellation();
|
|
6674
|
+
else signal?.addEventListener("abort", forwardCancellation, { once: true });
|
|
6675
|
+
let timeoutError = null;
|
|
6676
|
+
let streamedFailureHint = null;
|
|
6677
|
+
const timeoutMs = getDockerBuildIdleTimeoutMs();
|
|
6678
|
+
const idleMonitor = startDockerBuildIdleMonitor({
|
|
6679
|
+
timeoutMs,
|
|
6680
|
+
onIdle: (idleMs) => {
|
|
6681
|
+
log.log(`Still building... (no output for ${Math.floor(idleMs / 6e4)}m)`);
|
|
6285
6682
|
},
|
|
6286
|
-
|
|
6287
|
-
|
|
6683
|
+
onTimeout: () => {
|
|
6684
|
+
timeoutError = dockerBuildIdleTimeoutError(timeoutMs, diagnosticContext);
|
|
6685
|
+
buildContainerTracker?.flush();
|
|
6686
|
+
if (buildContainerTracker) void buildContainerTracker.terminateActive();
|
|
6687
|
+
commandAbort.abort(timeoutError);
|
|
6688
|
+
}
|
|
6689
|
+
});
|
|
6690
|
+
let code = null;
|
|
6691
|
+
let execError = null;
|
|
6692
|
+
try {
|
|
6693
|
+
const result = await executor.streamExec(
|
|
6694
|
+
buildCmd,
|
|
6695
|
+
(entry) => {
|
|
6696
|
+
idleMonitor.progress();
|
|
6697
|
+
buildContainerTracker?.observeChunk(entry.message);
|
|
6698
|
+
streamedFailureHint = chooseDockerBuildFailureHint(
|
|
6699
|
+
streamedFailureHint,
|
|
6700
|
+
this.extractBuildFailureHint(entry.message, diagnosticContext)
|
|
6701
|
+
);
|
|
6702
|
+
log.log(entry.message, parseLogLevel(entry.message));
|
|
6703
|
+
},
|
|
6704
|
+
{ signal: commandAbort.signal }
|
|
6705
|
+
);
|
|
6706
|
+
code = result.code;
|
|
6707
|
+
} catch (error) {
|
|
6708
|
+
execError = error;
|
|
6709
|
+
} finally {
|
|
6710
|
+
idleMonitor.stop();
|
|
6711
|
+
buildContainerTracker?.flush();
|
|
6712
|
+
signal?.removeEventListener("abort", forwardCancellation);
|
|
6713
|
+
}
|
|
6288
6714
|
log.log("\u2500\u2500\u2500 end docker build output \u2500\u2500\u2500");
|
|
6289
|
-
if (
|
|
6290
|
-
|
|
6715
|
+
if (timeoutError) {
|
|
6716
|
+
await stopTrackedBuildContainer(buildContainerTracker, log, "timed-out");
|
|
6717
|
+
throw timeoutError;
|
|
6718
|
+
}
|
|
6719
|
+
if (signal?.aborted) {
|
|
6720
|
+
await stopTrackedBuildContainer(buildContainerTracker, log, "interrupted");
|
|
6721
|
+
throw new BuildCancelledError();
|
|
6722
|
+
}
|
|
6723
|
+
if (execError) {
|
|
6724
|
+
await stopTrackedBuildContainer(buildContainerTracker, log, "interrupted");
|
|
6725
|
+
throw execError;
|
|
6726
|
+
}
|
|
6727
|
+
if (code === null) {
|
|
6728
|
+
await stopTrackedBuildContainer(buildContainerTracker, log, "interrupted");
|
|
6729
|
+
throw new Error("docker build ended without an exit code");
|
|
6730
|
+
}
|
|
6731
|
+
if (code !== 0) {
|
|
6732
|
+
await stopTrackedBuildContainer(buildContainerTracker, log, "interrupted");
|
|
6733
|
+
throw new Error(dockerBuildExitMessage(code, streamedFailureHint, diagnosticContext));
|
|
6734
|
+
}
|
|
6291
6735
|
this.emitDockerStep(log, "install", "completed", "Image build finished");
|
|
6292
6736
|
}
|
|
6293
6737
|
/**
|
|
@@ -6338,48 +6782,64 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6338
6782
|
config.gitSsh
|
|
6339
6783
|
);
|
|
6340
6784
|
}
|
|
6341
|
-
const {
|
|
6342
|
-
cloneUrl,
|
|
6343
|
-
gitEnv: GIT_ENV,
|
|
6344
|
-
credFlag: CRED
|
|
6345
|
-
} = assembleGitClone({
|
|
6785
|
+
const gitInvocation = assembleGitClone({
|
|
6346
6786
|
repoUrl: config.repoUrl,
|
|
6347
6787
|
gitToken: config.gitToken,
|
|
6348
6788
|
gitCredentialHelperPath: config.gitCredentialHelperPath,
|
|
6349
6789
|
ssh: sshMaterial,
|
|
6350
6790
|
ambient: config.gitAmbient
|
|
6351
6791
|
});
|
|
6792
|
+
const { cloneUrl, credFlag: CRED } = gitInvocation;
|
|
6352
6793
|
const dir = sq(remoteContextDir);
|
|
6353
6794
|
const authLabel = config.gitSsh ? "ssh key" : useHelper ? "forwarded credentials" : config.gitAmbient ? `the server's own git credentials (${config.gitAmbient.via})` : "token";
|
|
6354
6795
|
log.log(`Cloning ${config.repoUrl} on the server \u2192 ${remoteContextDir} (${authLabel})...
|
|
6355
6796
|
`);
|
|
6356
6797
|
await executor.exec(`rm -rf ${dir} && mkdir -p ${dir}`);
|
|
6357
|
-
const run = async (cmd) => {
|
|
6798
|
+
const run = async (operation, cmd) => {
|
|
6358
6799
|
const { code } = await executor.streamExec(
|
|
6359
6800
|
cmd,
|
|
6360
6801
|
(entry) => log.log(entry.message, parseLogLevel(entry.message))
|
|
6361
6802
|
);
|
|
6362
|
-
if (code !== 0) throw new Error(`git
|
|
6803
|
+
if (code !== 0) throw new Error(`git ${operation} on server exited with code ${code}`);
|
|
6363
6804
|
};
|
|
6364
6805
|
try {
|
|
6365
6806
|
if (config.commitSha) {
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6807
|
+
await run(
|
|
6808
|
+
"clone",
|
|
6809
|
+
gitShellCommand(
|
|
6810
|
+
gitInvocation,
|
|
6811
|
+
`clone --progress --depth 50 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${dir}`
|
|
6812
|
+
)
|
|
6813
|
+
);
|
|
6814
|
+
const commitPresent = await executor.exec(`cd ${dir} && git ${CRED} cat-file -e ${sq(`${config.commitSha}^{commit}`)}`).then(
|
|
6815
|
+
() => true,
|
|
6816
|
+
() => false
|
|
6817
|
+
);
|
|
6818
|
+
if (!commitPresent) {
|
|
6371
6819
|
log.log(
|
|
6372
6820
|
`Commit ${config.commitSha} not in the shallow clone; unshallowing and retrying.
|
|
6373
6821
|
`,
|
|
6374
6822
|
"warn"
|
|
6375
6823
|
);
|
|
6376
6824
|
await run(
|
|
6377
|
-
|
|
6825
|
+
"fetch",
|
|
6826
|
+
`cd ${dir} && ${gitShellCommand(gitInvocation, "fetch --progress --unshallow")}`
|
|
6378
6827
|
);
|
|
6379
6828
|
}
|
|
6829
|
+
await run(
|
|
6830
|
+
"checkout",
|
|
6831
|
+
`cd ${dir} && ${gitShellCommand(
|
|
6832
|
+
gitInvocation,
|
|
6833
|
+
`-c advice.detachedHead=false checkout ${sq(config.commitSha)}`
|
|
6834
|
+
)}`
|
|
6835
|
+
);
|
|
6380
6836
|
} else {
|
|
6381
6837
|
await run(
|
|
6382
|
-
|
|
6838
|
+
"clone",
|
|
6839
|
+
gitShellCommand(
|
|
6840
|
+
gitInvocation,
|
|
6841
|
+
`clone --progress --depth 1 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${dir}`
|
|
6842
|
+
)
|
|
6383
6843
|
);
|
|
6384
6844
|
}
|
|
6385
6845
|
await executor.exec(`rm -rf ${sq(`${remoteContextDir}/.git`)}`).catch(() => {
|
|
@@ -6500,6 +6960,55 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6500
6960
|
}
|
|
6501
6961
|
return { options: { version: "2" }, trace: new BuildKitTraceDecoder() };
|
|
6502
6962
|
}
|
|
6963
|
+
/**
|
|
6964
|
+
* Bind legacy-builder cleanup to the exact container id Docker announces in
|
|
6965
|
+
* this build's own progress stream. A 404 is an ordinary "already cleaned"
|
|
6966
|
+
* result; every other inspection failure remains inconclusive and therefore
|
|
6967
|
+
* can never authorize a kill.
|
|
6968
|
+
*/
|
|
6969
|
+
legacyBuildContainerTracker(expectedMemoryBytes, ownershipHost) {
|
|
6970
|
+
return new LegacyBuildContainerTracker(
|
|
6971
|
+
{
|
|
6972
|
+
inspect: async (id) => {
|
|
6973
|
+
try {
|
|
6974
|
+
return await this.docker.getContainer(id).inspect();
|
|
6975
|
+
} catch (error) {
|
|
6976
|
+
if (isDockerNotFoundError(error)) return null;
|
|
6977
|
+
throw error;
|
|
6978
|
+
}
|
|
6979
|
+
},
|
|
6980
|
+
kill: async (id) => {
|
|
6981
|
+
await this.docker.getContainer(id).kill({ signal: "SIGKILL" });
|
|
6982
|
+
}
|
|
6983
|
+
},
|
|
6984
|
+
expectedMemoryBytes,
|
|
6985
|
+
ownershipHost
|
|
6986
|
+
);
|
|
6987
|
+
}
|
|
6988
|
+
/** Same proof model as the Engine-API tracker, using the already-live SSH executor. */
|
|
6989
|
+
remoteLegacyBuildContainerTracker(executor, ownershipHost) {
|
|
6990
|
+
return new LegacyBuildContainerTracker(
|
|
6991
|
+
{
|
|
6992
|
+
inspect: async (id) => {
|
|
6993
|
+
try {
|
|
6994
|
+
const output = await executor.exec(`docker container inspect ${sq(id)}`);
|
|
6995
|
+
const parsed = JSON.parse(output);
|
|
6996
|
+
return Array.isArray(parsed) && parsed[0] && typeof parsed[0] === "object" ? parsed[0] : null;
|
|
6997
|
+
} catch (error) {
|
|
6998
|
+
if (/No such (?:object|container)|not found/i.test(safeErrorMessage(error)))
|
|
6999
|
+
return null;
|
|
7000
|
+
throw error;
|
|
7001
|
+
}
|
|
7002
|
+
},
|
|
7003
|
+
kill: async (id) => {
|
|
7004
|
+
await executor.exec(`docker container kill --signal KILL ${sq(id)} >/dev/null`);
|
|
7005
|
+
}
|
|
7006
|
+
},
|
|
7007
|
+
// The native CLI path intentionally applies no OpenShip memory flag.
|
|
7008
|
+
void 0,
|
|
7009
|
+
ownershipHost
|
|
7010
|
+
);
|
|
7011
|
+
}
|
|
6503
7012
|
/**
|
|
6504
7013
|
* Dockerode build path. Used for local socket and TCP transports, plus
|
|
6505
7014
|
* SSH transports that didn't get an executor wired in (shouldn't happen
|
|
@@ -6511,13 +7020,21 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6511
7020
|
*/
|
|
6512
7021
|
async buildViaDockerode(config, buildContext, tag, log, cancelSignal) {
|
|
6513
7022
|
log.log(`Streaming build context to Docker daemon - image tag: ${tag}`);
|
|
6514
|
-
const { body, abortSignal, takeError } = packBuildContext(
|
|
7023
|
+
const { body, abortSignal, abort, detachCancellation, takeError } = packBuildContext(
|
|
6515
7024
|
buildContext.buildContextDir,
|
|
6516
7025
|
buildContext.contextEntries,
|
|
6517
7026
|
cancelSignal,
|
|
6518
7027
|
buildContext.ignoreContextPath
|
|
6519
7028
|
);
|
|
6520
7029
|
const builder = this.selectDockerodeBuilder(config, buildContext.requiresBuildKit, log);
|
|
7030
|
+
const buildLimits = dockerBuildResourceLimits(config.resources);
|
|
7031
|
+
const ownershipHost = builder.options.version !== "2" ? newBuildOwnershipHost() : null;
|
|
7032
|
+
const diagnosticContext = {
|
|
7033
|
+
configuredMemoryMb: config.resources.memoryMb,
|
|
7034
|
+
// Engine API build limits work only with the classic builder. The
|
|
7035
|
+
// BuildKit selection above already logs that its build runs uncapped.
|
|
7036
|
+
memoryLimitApplied: builder.options.version !== "2" && buildLimits.memory !== void 0
|
|
7037
|
+
};
|
|
6521
7038
|
try {
|
|
6522
7039
|
const stream = await this.docker.buildImage(body, {
|
|
6523
7040
|
t: tag,
|
|
@@ -6527,13 +7044,23 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6527
7044
|
// Omitted entirely unless the project set a build cap — a self-hosted
|
|
6528
7045
|
// build should be free to use the machine (a production build often
|
|
6529
7046
|
// needs several GB). Opt-in only; see dockerBuildResourceLimits.
|
|
6530
|
-
...
|
|
7047
|
+
...buildLimits,
|
|
7048
|
+
...ownershipHost && { extrahosts: `${ownershipHost}:127.0.0.1` },
|
|
6531
7049
|
forcerm: true,
|
|
6532
7050
|
...builder.options,
|
|
6533
7051
|
abortSignal
|
|
6534
7052
|
});
|
|
7053
|
+
detachCancellation();
|
|
6535
7054
|
log.log("Connected to Docker daemon. Build output follows:");
|
|
6536
|
-
await this.streamDockerodeBuild(stream, log,
|
|
7055
|
+
await this.streamDockerodeBuild(stream, log, {
|
|
7056
|
+
trace: builder.trace,
|
|
7057
|
+
diagnosticContext,
|
|
7058
|
+
abortBuild: abort,
|
|
7059
|
+
cancelSignal,
|
|
7060
|
+
...ownershipHost && {
|
|
7061
|
+
legacyBuilder: { expectedMemoryBytes: buildLimits.memory, ownershipHost }
|
|
7062
|
+
}
|
|
7063
|
+
});
|
|
6537
7064
|
log.log("Docker daemon finished streaming build output. Finalizing image...\n");
|
|
6538
7065
|
} catch (err) {
|
|
6539
7066
|
const contextErr = takeError();
|
|
@@ -6541,6 +7068,7 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6541
7068
|
cause: contextErr
|
|
6542
7069
|
}) : err;
|
|
6543
7070
|
} finally {
|
|
7071
|
+
detachCancellation();
|
|
6544
7072
|
await buildContext.cleanup();
|
|
6545
7073
|
}
|
|
6546
7074
|
}
|
|
@@ -6549,69 +7077,107 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
6549
7077
|
* idle timeout + keepalive, and throw on a fatal build event. Shared by the
|
|
6550
7078
|
* single-image dockerode path and the local batch build (buildImages).
|
|
6551
7079
|
*/
|
|
6552
|
-
async streamDockerodeBuild(stream, log,
|
|
7080
|
+
async streamDockerodeBuild(stream, log, options = {}) {
|
|
6553
7081
|
let fatalBuildError = null;
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
new Error(
|
|
6593
|
-
"Docker build produced no output for 30 minutes. This usually means the remote server cannot reach the package registry, has broken DNS, or the Docker daemon stalled during the build."
|
|
6594
|
-
)
|
|
7082
|
+
const timeoutMs = getDockerBuildIdleTimeoutMs();
|
|
7083
|
+
const diagnosticContext = options.diagnosticContext ?? {};
|
|
7084
|
+
const buildContainerTracker = options.legacyBuilder ? this.legacyBuildContainerTracker(
|
|
7085
|
+
options.legacyBuilder.expectedMemoryBytes,
|
|
7086
|
+
options.legacyBuilder.ownershipHost
|
|
7087
|
+
) : null;
|
|
7088
|
+
let inactivityTimedOut = false;
|
|
7089
|
+
try {
|
|
7090
|
+
await new Promise((resolve, reject) => {
|
|
7091
|
+
let settled = false;
|
|
7092
|
+
let idleMonitor = null;
|
|
7093
|
+
let cancellationTimer = null;
|
|
7094
|
+
const clearTimers = () => {
|
|
7095
|
+
idleMonitor?.stop();
|
|
7096
|
+
if (cancellationTimer) clearTimeout(cancellationTimer);
|
|
7097
|
+
options.cancelSignal?.removeEventListener("abort", requestCancellation);
|
|
7098
|
+
};
|
|
7099
|
+
const fail = (error) => {
|
|
7100
|
+
if (settled) return;
|
|
7101
|
+
settled = true;
|
|
7102
|
+
clearTimers();
|
|
7103
|
+
stream.destroy?.(error);
|
|
7104
|
+
reject(error);
|
|
7105
|
+
};
|
|
7106
|
+
const abortCancelledBuild = () => {
|
|
7107
|
+
if (settled) return;
|
|
7108
|
+
buildContainerTracker?.flush();
|
|
7109
|
+
if (buildContainerTracker) void buildContainerTracker.terminateActive();
|
|
7110
|
+
const error = new BuildCancelledError();
|
|
7111
|
+
fail(error);
|
|
7112
|
+
options.abortBuild?.(error);
|
|
7113
|
+
};
|
|
7114
|
+
const requestCancellation = () => {
|
|
7115
|
+
if (settled || cancellationTimer) return;
|
|
7116
|
+
idleMonitor?.stop();
|
|
7117
|
+
cancellationTimer = setTimeout(
|
|
7118
|
+
abortCancelledBuild,
|
|
7119
|
+
buildContainerTracker ? BUILD_CONTAINER_IDENTIFICATION_GRACE_MS : 0
|
|
6595
7120
|
);
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
(err) => {
|
|
6603
|
-
if (err) {
|
|
6604
|
-
fail(err);
|
|
7121
|
+
cancellationTimer.unref?.();
|
|
7122
|
+
};
|
|
7123
|
+
const succeed = () => {
|
|
7124
|
+
if (settled) return;
|
|
7125
|
+
if (options.cancelSignal?.aborted) {
|
|
7126
|
+
abortCancelledBuild();
|
|
6605
7127
|
return;
|
|
6606
7128
|
}
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
7129
|
+
settled = true;
|
|
7130
|
+
clearTimers();
|
|
7131
|
+
resolve();
|
|
7132
|
+
};
|
|
7133
|
+
idleMonitor = startDockerBuildIdleMonitor({
|
|
7134
|
+
timeoutMs,
|
|
7135
|
+
onIdle: (idleMs) => {
|
|
7136
|
+
log.log(`Still building... (no output for ${Math.floor(idleMs / 6e4)}m)`);
|
|
7137
|
+
},
|
|
7138
|
+
onTimeout: () => {
|
|
7139
|
+
const error = dockerBuildIdleTimeoutError(timeoutMs, diagnosticContext);
|
|
7140
|
+
inactivityTimedOut = true;
|
|
7141
|
+
if (buildContainerTracker) {
|
|
7142
|
+
void buildContainerTracker.terminateActive();
|
|
7143
|
+
}
|
|
7144
|
+
fail(error);
|
|
7145
|
+
options.abortBuild?.(error);
|
|
7146
|
+
}
|
|
7147
|
+
});
|
|
7148
|
+
if (options.cancelSignal?.aborted) requestCancellation();
|
|
7149
|
+
else options.cancelSignal?.addEventListener("abort", requestCancellation, { once: true });
|
|
7150
|
+
this.docker.modem.followProgress(
|
|
7151
|
+
stream,
|
|
7152
|
+
(err) => {
|
|
7153
|
+
if (err) {
|
|
7154
|
+
fail(err);
|
|
7155
|
+
return;
|
|
7156
|
+
}
|
|
7157
|
+
succeed();
|
|
7158
|
+
},
|
|
7159
|
+
(event) => {
|
|
7160
|
+
idleMonitor?.progress();
|
|
7161
|
+
buildContainerTracker?.observe(event.stream);
|
|
7162
|
+
fatalBuildError = chooseDockerBuildFailureHint(
|
|
7163
|
+
fatalBuildError,
|
|
7164
|
+
this.handleBuildEvent(event, log, options.trace, diagnosticContext)
|
|
7165
|
+
);
|
|
7166
|
+
}
|
|
7167
|
+
);
|
|
7168
|
+
});
|
|
7169
|
+
} catch (error) {
|
|
7170
|
+
await stopTrackedBuildContainer(
|
|
7171
|
+
buildContainerTracker,
|
|
7172
|
+
log,
|
|
7173
|
+
inactivityTimedOut ? "timed-out" : "interrupted"
|
|
6613
7174
|
);
|
|
6614
|
-
|
|
7175
|
+
throw error;
|
|
7176
|
+
}
|
|
7177
|
+
if (options.cancelSignal?.aborted) {
|
|
7178
|
+
await stopTrackedBuildContainer(buildContainerTracker, log, "interrupted");
|
|
7179
|
+
throw new BuildCancelledError();
|
|
7180
|
+
}
|
|
6615
7181
|
if (fatalBuildError) {
|
|
6616
7182
|
throw new Error(fatalBuildError);
|
|
6617
7183
|
}
|
|
@@ -7338,13 +7904,19 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
7338
7904
|
{ signal, requiresBuildKit }
|
|
7339
7905
|
);
|
|
7340
7906
|
} else {
|
|
7341
|
-
const { body, abortSignal, takeError } = packBuildContext(
|
|
7907
|
+
const { body, abortSignal, abort, detachCancellation, takeError } = packBuildContext(
|
|
7342
7908
|
buildContextDir ?? tree.contextDir,
|
|
7343
7909
|
contextEntries ?? [],
|
|
7344
7910
|
signal,
|
|
7345
7911
|
ignoreContextPath
|
|
7346
7912
|
);
|
|
7347
7913
|
const builder = this.selectDockerodeBuilder(spec.config, requiresBuildKit, spec.logger);
|
|
7914
|
+
const buildLimits = dockerBuildResourceLimits(spec.config.resources);
|
|
7915
|
+
const ownershipHost = builder.options.version !== "2" ? newBuildOwnershipHost() : null;
|
|
7916
|
+
const diagnosticContext = {
|
|
7917
|
+
configuredMemoryMb: spec.config.resources.memoryMb,
|
|
7918
|
+
memoryLimitApplied: builder.options.version !== "2" && buildLimits.memory !== void 0
|
|
7919
|
+
};
|
|
7348
7920
|
try {
|
|
7349
7921
|
const stream = await this.docker.buildImage(body, {
|
|
7350
7922
|
t: tag,
|
|
@@ -7354,18 +7926,30 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
7354
7926
|
sessionId: spec.config.sessionId
|
|
7355
7927
|
}),
|
|
7356
7928
|
buildargs: resolveDockerBuildArgs(spec.config),
|
|
7357
|
-
...
|
|
7929
|
+
...buildLimits,
|
|
7930
|
+
...ownershipHost && { extrahosts: `${ownershipHost}:127.0.0.1` },
|
|
7358
7931
|
forcerm: true,
|
|
7359
7932
|
...builder.options,
|
|
7360
7933
|
abortSignal
|
|
7361
7934
|
});
|
|
7362
|
-
|
|
7935
|
+
detachCancellation();
|
|
7936
|
+
await this.streamDockerodeBuild(stream, spec.logger, {
|
|
7937
|
+
trace: builder.trace,
|
|
7938
|
+
diagnosticContext,
|
|
7939
|
+
abortBuild: abort,
|
|
7940
|
+
cancelSignal: signal,
|
|
7941
|
+
...ownershipHost && {
|
|
7942
|
+
legacyBuilder: { expectedMemoryBytes: buildLimits.memory, ownershipHost }
|
|
7943
|
+
}
|
|
7944
|
+
});
|
|
7363
7945
|
} catch (err) {
|
|
7364
7946
|
const contextErr = takeError();
|
|
7365
7947
|
throw contextErr ? new Error(
|
|
7366
7948
|
`Failed to read Docker build context: ${safeErrorMessage(contextErr)}`,
|
|
7367
7949
|
{ cause: contextErr }
|
|
7368
7950
|
) : err;
|
|
7951
|
+
} finally {
|
|
7952
|
+
detachCancellation();
|
|
7369
7953
|
}
|
|
7370
7954
|
}
|
|
7371
7955
|
if (isCancelled(spec.config.sessionId)) {
|
|
@@ -7685,6 +8269,56 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
7685
8269
|
} catch {
|
|
7686
8270
|
}
|
|
7687
8271
|
}
|
|
8272
|
+
/**
|
|
8273
|
+
* Reclaim BuildKit/classic-builder cache through Docker Engine's native API.
|
|
8274
|
+
*
|
|
8275
|
+
* Build cache belongs to the DAEMON, not to a project: intermediate records
|
|
8276
|
+
* do not reliably retain OpenShip's final-image labels. Callers therefore own
|
|
8277
|
+
* the host-wide policy (bounded automatic retention vs an explicit full
|
|
8278
|
+
* clear). Docker itself excludes records leased by an active build.
|
|
8279
|
+
*
|
|
8280
|
+
* dockerode 4.0.9's `pruneBuilder(opts)` silently discards `opts`, so use its
|
|
8281
|
+
* modem directly until upstream forwards the query parameters.
|
|
8282
|
+
*/
|
|
8283
|
+
async pruneBuildCache(options = {}) {
|
|
8284
|
+
const keepStorageBytes = options.keepStorageBytes;
|
|
8285
|
+
if (keepStorageBytes !== void 0 && (!Number.isSafeInteger(keepStorageBytes) || keepStorageBytes < 0)) {
|
|
8286
|
+
throw new RangeError("keepStorageBytes must be a non-negative safe integer");
|
|
8287
|
+
}
|
|
8288
|
+
const query = {
|
|
8289
|
+
all: options.all ?? false
|
|
8290
|
+
};
|
|
8291
|
+
if (keepStorageBytes !== void 0) query["keep-storage"] = keepStorageBytes;
|
|
8292
|
+
if (options.filters && Object.keys(options.filters).length > 0) {
|
|
8293
|
+
query.filters = options.filters;
|
|
8294
|
+
}
|
|
8295
|
+
const raw = await new Promise((resolve, reject) => {
|
|
8296
|
+
this.docker.modem.dial(
|
|
8297
|
+
{
|
|
8298
|
+
path: "/build/prune?",
|
|
8299
|
+
method: "POST",
|
|
8300
|
+
// `_body: {}` prevents docker-modem from serializing the query object
|
|
8301
|
+
// into an irrelevant POST body while preserving its normal query
|
|
8302
|
+
// encoding (including JSON-encoded filter maps).
|
|
8303
|
+
options: { _query: query, _body: {} },
|
|
8304
|
+
abortSignal: options.abortSignal,
|
|
8305
|
+
statusCodes: {
|
|
8306
|
+
200: true,
|
|
8307
|
+
500: "server error"
|
|
8308
|
+
}
|
|
8309
|
+
},
|
|
8310
|
+
(err, data) => {
|
|
8311
|
+
if (err) reject(err);
|
|
8312
|
+
else resolve(data);
|
|
8313
|
+
}
|
|
8314
|
+
);
|
|
8315
|
+
});
|
|
8316
|
+
const response = raw ?? {};
|
|
8317
|
+
return {
|
|
8318
|
+
cachesDeleted: Array.isArray(response.CachesDeleted) ? response.CachesDeleted.filter((id) => typeof id === "string") : [],
|
|
8319
|
+
spaceReclaimed: typeof response.SpaceReclaimed === "number" && Number.isFinite(response.SpaceReclaimed) && response.SpaceReclaimed >= 0 ? response.SpaceReclaimed : 0
|
|
8320
|
+
};
|
|
8321
|
+
}
|
|
7688
8322
|
/**
|
|
7689
8323
|
* Containers labeled for this deployment, with live state — the reconcile
|
|
7690
8324
|
* read-back. `State` is dockerode's `running | exited | paused | ...`; map it
|
|
@@ -8725,12 +9359,6 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
8725
9359
|
const log = onLog ?? (() => {
|
|
8726
9360
|
});
|
|
8727
9361
|
const containerName = `openship-${config.slug}-${config.serviceName}`;
|
|
8728
|
-
try {
|
|
8729
|
-
const existing = this.docker.getContainer(containerName);
|
|
8730
|
-
await gracefulStopForGrace(existing);
|
|
8731
|
-
await existing.remove({ force: true });
|
|
8732
|
-
} catch {
|
|
8733
|
-
}
|
|
8734
9362
|
const serviceEnv = splitRuntimeEnv(config.environment);
|
|
8735
9363
|
if (serviceEnv.dropped.length > 0) {
|
|
8736
9364
|
log({
|
|
@@ -8758,6 +9386,31 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
8758
9386
|
const restartPolicy = resolveRestartPolicy(config.restart);
|
|
8759
9387
|
const healthcheck = toDockerHealthcheck(config.advanced?.healthcheck);
|
|
8760
9388
|
const stopConfig = toStopConfig(config.advanced);
|
|
9389
|
+
if (!config.imageAlreadyPrepared) {
|
|
9390
|
+
try {
|
|
9391
|
+
log({
|
|
9392
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9393
|
+
message: `Pulling image ${config.image}...
|
|
9394
|
+
`,
|
|
9395
|
+
level: "info"
|
|
9396
|
+
});
|
|
9397
|
+
await this.pullImage(config.image, { force: config.forcePull });
|
|
9398
|
+
} catch (err) {
|
|
9399
|
+
log({
|
|
9400
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9401
|
+
message: `Failed to pull ${config.image}: ${err}
|
|
9402
|
+
`,
|
|
9403
|
+
level: "error"
|
|
9404
|
+
});
|
|
9405
|
+
throw err;
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
try {
|
|
9409
|
+
const existing = this.docker.getContainer(containerName);
|
|
9410
|
+
await gracefulStopForGrace(existing);
|
|
9411
|
+
await existing.remove({ force: true });
|
|
9412
|
+
} catch {
|
|
9413
|
+
}
|
|
8761
9414
|
log({
|
|
8762
9415
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8763
9416
|
message: `Creating service container ${containerName} from ${config.image}...
|
|
@@ -8787,25 +9440,6 @@ var DockerRuntime = class _DockerRuntime {
|
|
|
8787
9440
|
level: "info"
|
|
8788
9441
|
});
|
|
8789
9442
|
}
|
|
8790
|
-
if (!config.image.startsWith("openship/")) {
|
|
8791
|
-
try {
|
|
8792
|
-
log({
|
|
8793
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8794
|
-
message: `Pulling image ${config.image}...
|
|
8795
|
-
`,
|
|
8796
|
-
level: "info"
|
|
8797
|
-
});
|
|
8798
|
-
await this.pullImage(config.image, { force: config.forcePull });
|
|
8799
|
-
} catch (err) {
|
|
8800
|
-
log({
|
|
8801
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8802
|
-
message: `Failed to pull ${config.image}: ${err}
|
|
8803
|
-
`,
|
|
8804
|
-
level: "error"
|
|
8805
|
-
});
|
|
8806
|
-
throw err;
|
|
8807
|
-
}
|
|
8808
|
-
}
|
|
8809
9443
|
const container = await this.docker.createContainer({
|
|
8810
9444
|
name: containerName,
|
|
8811
9445
|
Image: config.image,
|