@vercel/container 7.0.3 → 8.1.0
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/index.js +1356 -259
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ __export(src_exports, {
|
|
|
38
38
|
version: () => version
|
|
39
39
|
});
|
|
40
40
|
module.exports = __toCommonJS(src_exports);
|
|
41
|
-
var
|
|
41
|
+
var import_build_utils6 = require("@vercel/build-utils");
|
|
42
42
|
|
|
43
43
|
// src/diagnostics.ts
|
|
44
44
|
var import_build_utils = require("@vercel/build-utils");
|
|
@@ -62,17 +62,25 @@ async function generateProjectManifest({
|
|
|
62
62
|
var diagnostics = (0, import_build_utils.createDiagnostics)("container");
|
|
63
63
|
|
|
64
64
|
// src/index.ts
|
|
65
|
-
var
|
|
66
|
-
var
|
|
65
|
+
var import_node_fs14 = require("fs");
|
|
66
|
+
var import_node_path14 = __toESM(require("path"));
|
|
67
67
|
|
|
68
68
|
// src/util.ts
|
|
69
69
|
var import_build_utils2 = require("@vercel/build-utils");
|
|
70
70
|
var import_node_child_process = require("child_process");
|
|
71
|
-
var import_node_crypto = require("crypto");
|
|
72
71
|
var import_node_fs = require("fs");
|
|
73
72
|
var import_node_os = require("os");
|
|
74
73
|
var import_node_path = require("path");
|
|
75
74
|
var DEBUG = Boolean((0, import_build_utils2.getPlatformEnv)("BUILDER_DEBUG"));
|
|
75
|
+
function normalizeCommand(command) {
|
|
76
|
+
if (typeof command === "string") {
|
|
77
|
+
return [command];
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(command) && command.every((item) => typeof item === "string")) {
|
|
80
|
+
return command;
|
|
81
|
+
}
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
76
84
|
function write(line) {
|
|
77
85
|
process.stderr.write(`${line}
|
|
78
86
|
`);
|
|
@@ -125,9 +133,10 @@ function devImageTag(serviceName) {
|
|
|
125
133
|
return `vercel-dev/${safe || "service"}:dev`;
|
|
126
134
|
}
|
|
127
135
|
function run(cmd, args, opts = {}) {
|
|
128
|
-
return new Promise((
|
|
136
|
+
return new Promise((resolve2, reject) => {
|
|
129
137
|
const child = (0, import_node_child_process.spawn)(cmd, args, {
|
|
130
138
|
cwd: opts.cwd,
|
|
139
|
+
env: opts.env,
|
|
131
140
|
stdio: [opts.input !== void 0 ? "pipe" : "ignore", "pipe", "pipe"]
|
|
132
141
|
});
|
|
133
142
|
let stdout = "";
|
|
@@ -159,15 +168,15 @@ function run(cmd, args, opts = {}) {
|
|
|
159
168
|
});
|
|
160
169
|
child.on("close", (code) => {
|
|
161
170
|
if (code === 0) {
|
|
162
|
-
|
|
171
|
+
resolve2({ stdout, stderr });
|
|
163
172
|
} else {
|
|
164
173
|
const detail = stderr.trim().split("\n").slice(-5).join("\n");
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
`\`${cmd} ${args.join(" ")}\` exited with code ${code}` + (detail ? `
|
|
174
|
+
const error = new Error(
|
|
175
|
+
`\`${cmd} ${args.join(" ")}\` exited with code ${code}` + (detail ? `
|
|
168
176
|
${detail}` : "")
|
|
169
|
-
)
|
|
170
177
|
);
|
|
178
|
+
error.exitCode = code ?? void 0;
|
|
179
|
+
reject(error);
|
|
171
180
|
}
|
|
172
181
|
});
|
|
173
182
|
if (opts.input !== void 0) {
|
|
@@ -179,59 +188,6 @@ function extractField(text, label) {
|
|
|
179
188
|
const match = text.match(new RegExp(`^\\s*${label}:\\s*(.+)$`, "m"));
|
|
180
189
|
return match?.[1]?.trim();
|
|
181
190
|
}
|
|
182
|
-
function tokenFingerprint(token) {
|
|
183
|
-
if (!token)
|
|
184
|
-
return "absent";
|
|
185
|
-
const sha = (0, import_node_crypto.createHash)("sha256").update(token).digest("hex").slice(0, 8);
|
|
186
|
-
return `present(len=${token.length}, sha256=${sha})`;
|
|
187
|
-
}
|
|
188
|
-
function debugTokenClaims(label, token) {
|
|
189
|
-
if (!DEBUG)
|
|
190
|
-
return;
|
|
191
|
-
if (!token) {
|
|
192
|
-
debug(`${label}: <absent>`);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
const payload = token.split(".")[1];
|
|
197
|
-
if (!payload) {
|
|
198
|
-
debug(`${label}: <not a JWT>`);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
const claims = JSON.parse(
|
|
202
|
-
Buffer.from(payload, "base64url").toString("utf8")
|
|
203
|
-
);
|
|
204
|
-
const safe = {
|
|
205
|
-
iss: claims.iss,
|
|
206
|
-
aud: claims.aud,
|
|
207
|
-
sub: claims.sub,
|
|
208
|
-
scope: claims.scope,
|
|
209
|
-
owner: claims.owner,
|
|
210
|
-
owner_id: claims.owner_id,
|
|
211
|
-
project: claims.project,
|
|
212
|
-
project_id: claims.project_id,
|
|
213
|
-
exp: typeof claims.exp === "number" ? `${new Date(claims.exp * 1e3).toISOString()} (in ${Math.round(
|
|
214
|
-
(claims.exp * 1e3 - Date.now()) / 1e3
|
|
215
|
-
)}s)` : claims.exp
|
|
216
|
-
};
|
|
217
|
-
debug(`${label}: ${JSON.stringify(safe)}`);
|
|
218
|
-
} catch (err) {
|
|
219
|
-
debug(`${label}: <unparseable claims> (${err.message})`);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
function decodeOidcClaims(token) {
|
|
223
|
-
if (!token)
|
|
224
|
-
return {};
|
|
225
|
-
try {
|
|
226
|
-
const payload = token.split(".")[1];
|
|
227
|
-
if (!payload)
|
|
228
|
-
return {};
|
|
229
|
-
const json = Buffer.from(payload, "base64url").toString("utf8");
|
|
230
|
-
return JSON.parse(json);
|
|
231
|
-
} catch {
|
|
232
|
-
return {};
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
191
|
function isBuildContainer() {
|
|
236
192
|
return Boolean(readString(process.env.VERCEL_BUILD_IMAGE));
|
|
237
193
|
}
|
|
@@ -401,6 +357,7 @@ Continuing (set VERCEL_VCR_STRICT_STORAGE=1 to fail builds).`
|
|
|
401
357
|
}
|
|
402
358
|
|
|
403
359
|
// src/oidc.ts
|
|
360
|
+
var import_node_crypto = require("crypto");
|
|
404
361
|
function parseOidcToken(token) {
|
|
405
362
|
const parts = token.split(".");
|
|
406
363
|
if (parts.length !== 3) {
|
|
@@ -415,6 +372,59 @@ function parseOidcToken(token) {
|
|
|
415
372
|
throw new Error("VERCEL_OIDC_TOKEN has an unreadable JWT payload.");
|
|
416
373
|
}
|
|
417
374
|
}
|
|
375
|
+
function decodeOidcClaims(token) {
|
|
376
|
+
if (!token)
|
|
377
|
+
return {};
|
|
378
|
+
try {
|
|
379
|
+
const payload = token.split(".")[1];
|
|
380
|
+
if (!payload)
|
|
381
|
+
return {};
|
|
382
|
+
const json = Buffer.from(payload, "base64url").toString("utf8");
|
|
383
|
+
return JSON.parse(json);
|
|
384
|
+
} catch {
|
|
385
|
+
return {};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function tokenFingerprint(token) {
|
|
389
|
+
if (!token)
|
|
390
|
+
return "absent";
|
|
391
|
+
const sha = (0, import_node_crypto.createHash)("sha256").update(token).digest("hex").slice(0, 8);
|
|
392
|
+
return `present(len=${token.length}, sha256=${sha})`;
|
|
393
|
+
}
|
|
394
|
+
function debugTokenClaims(label, token) {
|
|
395
|
+
if (!DEBUG)
|
|
396
|
+
return;
|
|
397
|
+
if (!token) {
|
|
398
|
+
debug(`${label}: <absent>`);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
const payload = token.split(".")[1];
|
|
403
|
+
if (!payload) {
|
|
404
|
+
debug(`${label}: <not a JWT>`);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const claims = JSON.parse(
|
|
408
|
+
Buffer.from(payload, "base64url").toString("utf8")
|
|
409
|
+
);
|
|
410
|
+
const safe = {
|
|
411
|
+
iss: claims.iss,
|
|
412
|
+
aud: claims.aud,
|
|
413
|
+
sub: claims.sub,
|
|
414
|
+
scope: claims.scope,
|
|
415
|
+
owner: claims.owner,
|
|
416
|
+
owner_id: claims.owner_id,
|
|
417
|
+
project: claims.project,
|
|
418
|
+
project_id: claims.project_id,
|
|
419
|
+
exp: typeof claims.exp === "number" ? `${new Date(claims.exp * 1e3).toISOString()} (in ${Math.round(
|
|
420
|
+
(claims.exp * 1e3 - Date.now()) / 1e3
|
|
421
|
+
)}s)` : claims.exp
|
|
422
|
+
};
|
|
423
|
+
debug(`${label}: ${JSON.stringify(safe)}`);
|
|
424
|
+
} catch (err) {
|
|
425
|
+
debug(`${label}: <unparseable claims> (${err.message})`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
418
428
|
function resolveProjectContext(token) {
|
|
419
429
|
const claims = parseOidcToken(token);
|
|
420
430
|
return {
|
|
@@ -507,6 +517,7 @@ function formatVcrAuthError(registry, username, detail) {
|
|
|
507
517
|
// src/engines/types.ts
|
|
508
518
|
var VCR_REGISTRY = process.env.VERCEL_VCR_REGISTRY || "vcr.vercel.com";
|
|
509
519
|
var TARGET_PLATFORM = "linux/amd64";
|
|
520
|
+
var DIGEST_RE = /sha256:[a-f0-9]{64}/;
|
|
510
521
|
function buildArgFlags(params) {
|
|
511
522
|
const flags = [];
|
|
512
523
|
for (const [key, value] of Object.entries(params.buildArgs ?? {})) {
|
|
@@ -808,7 +819,7 @@ async function startDockerDaemon(span) {
|
|
|
808
819
|
].join("\n")
|
|
809
820
|
);
|
|
810
821
|
}
|
|
811
|
-
await new Promise((
|
|
822
|
+
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
812
823
|
}
|
|
813
824
|
}
|
|
814
825
|
async function stopDockerDaemon(daemon, span) {
|
|
@@ -818,12 +829,12 @@ async function stopDockerDaemon(daemon, span) {
|
|
|
818
829
|
}
|
|
819
830
|
step("Stopping Docker daemon");
|
|
820
831
|
const stopTimeoutMs = Number(process.env.VERCEL_VCR_DOCKERD_STOP_TIMEOUT_MS) || 1e4;
|
|
821
|
-
await new Promise((
|
|
832
|
+
await new Promise((resolve2) => {
|
|
822
833
|
let settled = false;
|
|
823
834
|
const finish = () => {
|
|
824
835
|
if (!settled) {
|
|
825
836
|
settled = true;
|
|
826
|
-
|
|
837
|
+
resolve2();
|
|
827
838
|
}
|
|
828
839
|
};
|
|
829
840
|
child.once("exit", finish);
|
|
@@ -1026,7 +1037,39 @@ function selectContainerEngine() {
|
|
|
1026
1037
|
return isBuildContainer() ? buildahEngine : dockerEngine;
|
|
1027
1038
|
}
|
|
1028
1039
|
|
|
1040
|
+
// src/buildpacks/build.ts
|
|
1041
|
+
var import_node_fs10 = require("fs");
|
|
1042
|
+
var import_node_os7 = require("os");
|
|
1043
|
+
var import_node_path10 = require("path");
|
|
1044
|
+
|
|
1029
1045
|
// src/registry.ts
|
|
1046
|
+
async function resolveRegistryTarget(params) {
|
|
1047
|
+
const token = await withSpan(
|
|
1048
|
+
params.span,
|
|
1049
|
+
"container.mint_oidc",
|
|
1050
|
+
{},
|
|
1051
|
+
(s) => resolveOidcTokenForBuild(s)
|
|
1052
|
+
);
|
|
1053
|
+
const claims = decodeOidcClaims(token);
|
|
1054
|
+
debug(`registry token: ${tokenFingerprint(token)}`);
|
|
1055
|
+
debugTokenClaims("OIDC token claims", token);
|
|
1056
|
+
const username = claims.owner_id;
|
|
1057
|
+
if (!username) {
|
|
1058
|
+
throw new Error(
|
|
1059
|
+
"VERCEL_OIDC_TOKEN is missing the `owner_id` (team id) claim required to authenticate to the container registry."
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
const fullRepository = [claims.owner, claims.project, params.repository].join(
|
|
1063
|
+
"/"
|
|
1064
|
+
);
|
|
1065
|
+
return {
|
|
1066
|
+
token,
|
|
1067
|
+
claims,
|
|
1068
|
+
username,
|
|
1069
|
+
fullRepository,
|
|
1070
|
+
imageRef: `${VCR_REGISTRY}/${fullRepository}:${params.tag}`
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1030
1073
|
async function ensureRepository(repository, token, claims, span) {
|
|
1031
1074
|
if (repository.includes("/")) {
|
|
1032
1075
|
debug(`skipping repository auto-create (fully-qualified "${repository}")`);
|
|
@@ -1077,95 +1120,1156 @@ async function ensureRepository(repository, token, claims, span) {
|
|
|
1077
1120
|
}
|
|
1078
1121
|
}
|
|
1079
1122
|
|
|
1080
|
-
// src/
|
|
1081
|
-
var
|
|
1123
|
+
// src/buildpacks/lifecycle/lifecycle.ts
|
|
1124
|
+
var import_build_utils3 = require("@vercel/build-utils");
|
|
1125
|
+
var import_node_crypto2 = require("crypto");
|
|
1082
1126
|
var import_node_fs4 = require("fs");
|
|
1083
1127
|
var import_node_os4 = require("os");
|
|
1084
|
-
var import_node_path4 =
|
|
1085
|
-
var
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
"
|
|
1091
|
-
"
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1128
|
+
var import_node_path4 = require("path");
|
|
1129
|
+
var import_smol_toml = require("smol-toml");
|
|
1130
|
+
var import_tar = require("tar");
|
|
1131
|
+
|
|
1132
|
+
// src/buildpacks/distribution.json
|
|
1133
|
+
var distribution_default = {
|
|
1134
|
+
$comment: "Pinned lifecycle and buildpack archives",
|
|
1135
|
+
baseUrl: "https://lwnhvkbwb2b5igkl.public.blob.vercel-storage.com",
|
|
1136
|
+
lifecycle: {
|
|
1137
|
+
version: "0.21.18",
|
|
1138
|
+
sha256: "45e4ac394fe8194b909002571c25479dcb39f2ecabecad4105621369c279c78e"
|
|
1139
|
+
},
|
|
1140
|
+
buildpacks: {
|
|
1141
|
+
ruby: {
|
|
1142
|
+
id: "vercel/ruby",
|
|
1143
|
+
version: "0.1.0",
|
|
1144
|
+
sha256: "e204724b690ee3f64d28455d06efd3c7fce9189057ba4d3f702e27c24b8bdee5"
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
// src/buildpacks/distribution.ts
|
|
1150
|
+
var distribution = distribution_default;
|
|
1151
|
+
var BUILDPACK_DIST_BASE_URL = distribution.baseUrl;
|
|
1152
|
+
var LIFECYCLE_VERSION = distribution.lifecycle.version;
|
|
1153
|
+
var LIFECYCLE = {
|
|
1154
|
+
url: `${BUILDPACK_DIST_BASE_URL}/lifecycle/${LIFECYCLE_VERSION}/lifecycle-v${LIFECYCLE_VERSION}-linux-x86-64.tgz`,
|
|
1155
|
+
sha256: distribution.lifecycle.sha256
|
|
1156
|
+
};
|
|
1157
|
+
function vercelBuildpack(name) {
|
|
1158
|
+
const pinned = distribution.buildpacks[name];
|
|
1159
|
+
if (!pinned) {
|
|
1160
|
+
throw new Error(
|
|
1161
|
+
`distribution.json does not pin a "${name}" buildpack; add it under "buildpacks".`
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
return { name, ...pinned };
|
|
1112
1165
|
}
|
|
1113
|
-
function
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1166
|
+
function vercelBuildpackArtifact(buildpack) {
|
|
1167
|
+
const { name, version: version2 } = buildpack;
|
|
1168
|
+
return {
|
|
1169
|
+
url: `${BUILDPACK_DIST_BASE_URL}/buildpacks/${name}/${version2}/vercel-${name}-${version2}.cnb`,
|
|
1170
|
+
sha256: buildpack.sha256
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// src/buildpacks/lifecycle/lifecycle.ts
|
|
1175
|
+
var BUILD_USER_ID = 1001;
|
|
1176
|
+
var BUILD_GROUP_ID = 1001;
|
|
1177
|
+
var BUILD_USER = `${BUILD_USER_ID}:${BUILD_GROUP_ID}`;
|
|
1178
|
+
var CNB_PLATFORM_API = "0.13";
|
|
1179
|
+
var ORDER_MOUNT_DIR = "/platform/order";
|
|
1180
|
+
var ORDER_FILE = `${ORDER_MOUNT_DIR}/order.toml`;
|
|
1181
|
+
var ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1182
|
+
async function stageWorkspace({ files, workPath }, destinationDir) {
|
|
1183
|
+
const staged = /* @__PURE__ */ Object.create(null);
|
|
1184
|
+
const symlinks = /* @__PURE__ */ new Set();
|
|
1185
|
+
for (const [name, file] of Object.entries(files)) {
|
|
1186
|
+
const portable = name.replace(/\\/g, "/");
|
|
1187
|
+
const normalized = import_node_path4.posix.normalize(portable).replace(/\/$/, "");
|
|
1188
|
+
if (!normalized || normalized === "." || import_node_path4.win32.isAbsolute(name) || import_node_path4.posix.isAbsolute(portable) || portable.split("/").includes("..") || normalized in staged) {
|
|
1189
|
+
throw new Error(`Invalid buildpack source path ${JSON.stringify(name)}.`);
|
|
1190
|
+
}
|
|
1191
|
+
staged[normalized] = file;
|
|
1192
|
+
if (!(0, import_build_utils3.isSymbolicLink)(file.mode))
|
|
1119
1193
|
continue;
|
|
1194
|
+
let target = (0, import_build_utils3.getSymlinkTarget)(file);
|
|
1195
|
+
if (target === null && file.type === "FileRef") {
|
|
1196
|
+
target = (await (0, import_build_utils3.streamToBuffer)(await file.toStreamAsync())).toString();
|
|
1197
|
+
}
|
|
1198
|
+
if (!target) {
|
|
1199
|
+
throw new Error(`Cannot read buildpack source symlink ${name}.`);
|
|
1200
|
+
}
|
|
1201
|
+
if ((0, import_node_path4.isAbsolute)(target)) {
|
|
1202
|
+
target = (0, import_node_path4.relative)((0, import_node_path4.resolve)(workPath, import_node_path4.posix.dirname(normalized)), target) || ".";
|
|
1203
|
+
} else if (import_node_path4.win32.isAbsolute(target)) {
|
|
1204
|
+
throw new Error(
|
|
1205
|
+
`Buildpack source symlink ${name} has an absolute target.`
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
target = target.replace(/\\/g, "/");
|
|
1209
|
+
const resolved = import_node_path4.posix.join(import_node_path4.posix.dirname(normalized), target);
|
|
1210
|
+
if (resolved === ".." || resolved.startsWith("../")) {
|
|
1211
|
+
throw new Error(
|
|
1212
|
+
`Buildpack source symlink ${name} escapes the service root. Ruby buildpack services must be self-contained.`
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
staged[normalized] = new import_build_utils3.FileBlob({ data: target, mode: file.mode });
|
|
1216
|
+
symlinks.add(normalized);
|
|
1217
|
+
}
|
|
1218
|
+
for (const name of Object.keys(staged)) {
|
|
1219
|
+
for (let parent = import_node_path4.posix.dirname(name); parent !== "."; parent = import_node_path4.posix.dirname(parent)) {
|
|
1220
|
+
if (symlinks.has(parent)) {
|
|
1221
|
+
throw new Error(
|
|
1222
|
+
`Buildpack source ${name} is inside symlink ${parent}.`
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
const file = staged[name];
|
|
1227
|
+
if (file.type === "FileFsRef" && !(0, import_build_utils3.isSymbolicLink)(file.mode)) {
|
|
1228
|
+
const sourcePath = (0, import_node_path4.resolve)(file.fsPath);
|
|
1229
|
+
const sourceRelative = (0, import_node_path4.relative)((0, import_node_path4.resolve)(workPath), sourcePath);
|
|
1230
|
+
if (sourceRelative.startsWith(`..${import_node_path4.sep}`) || sourceRelative === ".." || (0, import_node_path4.isAbsolute)(sourceRelative)) {
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
const sourceRoot = import_node_fs4.realpathSync.native(workPath);
|
|
1234
|
+
const sourceTarget = import_node_fs4.realpathSync.native(sourcePath);
|
|
1235
|
+
if (sourceTarget !== (0, import_node_path4.resolve)(sourceRoot, sourceRelative)) {
|
|
1236
|
+
const target = (0, import_node_path4.relative)(sourceRoot, sourceTarget).split(import_node_path4.sep).join("/");
|
|
1237
|
+
if (target === ".." || target.startsWith("../") || (0, import_node_path4.isAbsolute)(target) || !(target in staged)) {
|
|
1238
|
+
throw new Error(
|
|
1239
|
+
`Buildpack source ${name} follows a symlink to an excluded or outside-root file. Ruby buildpack services must be self-contained.`
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
await (0, import_build_utils3.download)(staged, destinationDir);
|
|
1246
|
+
const root = import_node_fs4.realpathSync.native(destinationDir);
|
|
1247
|
+
for (const name of symlinks) {
|
|
1248
|
+
let target;
|
|
1249
|
+
try {
|
|
1250
|
+
target = (0, import_node_path4.relative)(root, import_node_fs4.realpathSync.native((0, import_node_path4.join)(destinationDir, name)));
|
|
1251
|
+
} catch {
|
|
1252
|
+
throw new Error(
|
|
1253
|
+
`Buildpack source symlink ${name} has no target in the staged service.`
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
if (target === ".." || target.startsWith(`..${import_node_path4.sep}`) || (0, import_node_path4.isAbsolute)(target)) {
|
|
1257
|
+
throw new Error(
|
|
1258
|
+
`Buildpack source symlink ${name} escapes the staged service.`
|
|
1259
|
+
);
|
|
1120
1260
|
}
|
|
1121
|
-
lines.push(`${key}=${value}`);
|
|
1122
1261
|
}
|
|
1123
|
-
(0, import_node_fs4.writeFileSync)(file, `${lines.join("\n")}
|
|
1124
|
-
`);
|
|
1125
|
-
return file;
|
|
1126
1262
|
}
|
|
1127
|
-
function
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1263
|
+
function writePlatformEnvDir(buildEnv, config, parentDir = (0, import_node_os4.tmpdir)()) {
|
|
1264
|
+
const environment = { ...buildEnv };
|
|
1265
|
+
delete environment.VERCEL_NODE_HOME;
|
|
1266
|
+
delete environment.VERCEL_COMMAND;
|
|
1267
|
+
if (config.nodeHome) {
|
|
1268
|
+
environment.VERCEL_NODE_HOME = config.nodeHome;
|
|
1269
|
+
}
|
|
1270
|
+
const dir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)(parentDir, "vercel-cnb-env-"));
|
|
1271
|
+
(0, import_node_fs4.chmodSync)(dir, 493);
|
|
1272
|
+
for (const [key, value] of Object.entries(environment)) {
|
|
1273
|
+
if (!ENV_NAME_RE.test(key)) {
|
|
1274
|
+
debug(`skipping build env var with an unsupported name: ${key}`);
|
|
1275
|
+
continue;
|
|
1133
1276
|
}
|
|
1277
|
+
const file = (0, import_node_path4.join)(dir, key);
|
|
1278
|
+
(0, import_node_fs4.writeFileSync)(file, value);
|
|
1279
|
+
(0, import_node_fs4.chmodSync)(file, 420);
|
|
1280
|
+
}
|
|
1281
|
+
return dir;
|
|
1282
|
+
}
|
|
1283
|
+
function readReportDigest(report) {
|
|
1284
|
+
let parsed;
|
|
1285
|
+
try {
|
|
1286
|
+
parsed = (0, import_smol_toml.parse)(report);
|
|
1134
1287
|
} catch {
|
|
1288
|
+
return void 0;
|
|
1135
1289
|
}
|
|
1136
|
-
|
|
1290
|
+
const digest = parsed?.image?.digest;
|
|
1291
|
+
if (typeof digest !== "string" || !DIGEST_RE.test(digest)) {
|
|
1292
|
+
return void 0;
|
|
1293
|
+
}
|
|
1294
|
+
return digest;
|
|
1137
1295
|
}
|
|
1138
|
-
function
|
|
1139
|
-
if (
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1296
|
+
function describeCreatorExitCode(code) {
|
|
1297
|
+
if (code === void 0)
|
|
1298
|
+
return void 0;
|
|
1299
|
+
if (code === 20)
|
|
1300
|
+
return "no buildpack detected the app";
|
|
1301
|
+
if (code === 21) {
|
|
1302
|
+
return "no buildpack detected the app and at least one errored during detection";
|
|
1303
|
+
}
|
|
1304
|
+
if (code >= 22 && code <= 29)
|
|
1305
|
+
return "the detect phase failed";
|
|
1306
|
+
if (code >= 30 && code <= 39)
|
|
1307
|
+
return "the analyze phase failed";
|
|
1308
|
+
if (code >= 40 && code <= 49)
|
|
1309
|
+
return "the restore phase failed";
|
|
1310
|
+
if (code === 51) {
|
|
1311
|
+
return "a buildpack failed while building the app (e.g. installing dependencies) \u2014 its error is in the build output above";
|
|
1312
|
+
}
|
|
1313
|
+
if (code >= 50 && code <= 59)
|
|
1314
|
+
return "the build phase failed";
|
|
1315
|
+
if (code >= 60 && code <= 69) {
|
|
1316
|
+
return "the export phase failed to write the image";
|
|
1145
1317
|
}
|
|
1318
|
+
return void 0;
|
|
1146
1319
|
}
|
|
1147
|
-
function
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
if (out.onStdout) {
|
|
1156
|
-
out.onStdout(chunk);
|
|
1157
|
-
} else {
|
|
1158
|
-
process.stderr.write(chunk.toString());
|
|
1159
|
-
}
|
|
1320
|
+
function writeOrderDir(buildpack, parentDir = (0, import_node_os4.tmpdir)()) {
|
|
1321
|
+
const order = {
|
|
1322
|
+
order: [
|
|
1323
|
+
{
|
|
1324
|
+
group: buildpack.buildpacks.map((entry) => ({
|
|
1325
|
+
id: entry.id,
|
|
1326
|
+
version: entry.version
|
|
1327
|
+
}))
|
|
1160
1328
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1329
|
+
]
|
|
1330
|
+
};
|
|
1331
|
+
const dir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)(parentDir, "vercel-cnb-order-"));
|
|
1332
|
+
(0, import_node_fs4.chmodSync)(dir, 493);
|
|
1333
|
+
const file = (0, import_node_path4.join)(dir, "order.toml");
|
|
1334
|
+
(0, import_node_fs4.writeFileSync)(file, (0, import_smol_toml.stringify)(order));
|
|
1335
|
+
(0, import_node_fs4.chmodSync)(file, 420);
|
|
1336
|
+
return dir;
|
|
1337
|
+
}
|
|
1338
|
+
function cnbRegistryAuth(credentials) {
|
|
1339
|
+
const { registry, username, token } = credentials;
|
|
1340
|
+
const basic = Buffer.from(`${username}:${token}`).toString("base64");
|
|
1341
|
+
return JSON.stringify({ [registry]: `Basic ${basic}` });
|
|
1342
|
+
}
|
|
1343
|
+
function assertPublishedArchive(name, archive) {
|
|
1344
|
+
if (/^0{64}$/.test(archive.sha256)) {
|
|
1345
|
+
throw new Error(
|
|
1346
|
+
`The ${name} archive has not been published. Publish it and update its checksum in distribution.json.`
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function assertPublishedDistribution(buildpack) {
|
|
1351
|
+
assertPublishedArchive("CNB lifecycle", LIFECYCLE);
|
|
1352
|
+
for (const entry of buildpack.buildpacks) {
|
|
1353
|
+
assertPublishedArchive(
|
|
1354
|
+
`${entry.id}@${entry.version} buildpack`,
|
|
1355
|
+
vercelBuildpackArtifact(entry)
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
function distributionUrls(buildpack) {
|
|
1360
|
+
return {
|
|
1361
|
+
lifecycle: LIFECYCLE.url,
|
|
1362
|
+
buildpacks: buildpack.buildpacks.map(
|
|
1363
|
+
(entry) => vercelBuildpackArtifact(entry).url
|
|
1364
|
+
)
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
async function downloadPinnedArchive(archive, destinationPath) {
|
|
1368
|
+
const response = await fetch(archive.url);
|
|
1369
|
+
if (!response.ok) {
|
|
1370
|
+
throw new Error(
|
|
1371
|
+
`Failed to fetch ${archive.url}: HTTP ${response.status} ${response.statusText}`
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
const contents = new Uint8Array(await response.arrayBuffer());
|
|
1375
|
+
const checksum = (0, import_node_crypto2.createHash)("sha256").update(contents).digest("hex");
|
|
1376
|
+
if (checksum !== archive.sha256) {
|
|
1377
|
+
throw new Error(
|
|
1378
|
+
`Checksum mismatch for ${archive.url}: expected ${archive.sha256}, received ${checksum}.`
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
(0, import_node_fs4.writeFileSync)(destinationPath, contents);
|
|
1382
|
+
}
|
|
1383
|
+
async function fetchPinnedArchive(archive, destinationDir) {
|
|
1384
|
+
const archivePath = (0, import_node_path4.join)(destinationDir, `${archive.sha256}.tgz`);
|
|
1385
|
+
try {
|
|
1386
|
+
await downloadPinnedArchive(archive, archivePath);
|
|
1387
|
+
(0, import_tar.extract)({ file: archivePath, cwd: destinationDir, sync: true });
|
|
1388
|
+
} finally {
|
|
1389
|
+
(0, import_node_fs4.rmSync)(archivePath, { force: true });
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
var LAYER_MEDIA_TYPE_RE = /(\.tar\.gzip|\.tar\+gzip|\.tar)$/;
|
|
1393
|
+
function readOciJson(path4) {
|
|
1394
|
+
return JSON.parse((0, import_node_fs4.readFileSync)(path4, "utf8"));
|
|
1395
|
+
}
|
|
1396
|
+
function blobPath(layoutDir, digest) {
|
|
1397
|
+
const match = digest.match(/^sha256:([0-9a-f]{64})$/);
|
|
1398
|
+
if (!match) {
|
|
1399
|
+
throw new Error(`Unsupported OCI digest ${JSON.stringify(digest)}.`);
|
|
1400
|
+
}
|
|
1401
|
+
return (0, import_node_path4.join)(layoutDir, "blobs", "sha256", match[1]);
|
|
1402
|
+
}
|
|
1403
|
+
async function fetchBuildpackage(archive, cnbDir) {
|
|
1404
|
+
const layoutDir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "vercel-cnb-buildpackage-"));
|
|
1405
|
+
const archivePath = (0, import_node_path4.join)(layoutDir, "buildpackage.cnb");
|
|
1406
|
+
try {
|
|
1407
|
+
await downloadPinnedArchive(archive, archivePath);
|
|
1408
|
+
await (0, import_tar.extract)({ file: archivePath, cwd: layoutDir });
|
|
1409
|
+
const index = readOciJson(
|
|
1410
|
+
(0, import_node_path4.join)(layoutDir, "index.json")
|
|
1411
|
+
);
|
|
1412
|
+
const manifestDigest = index.manifests?.[0]?.digest;
|
|
1413
|
+
if (!manifestDigest) {
|
|
1414
|
+
throw new Error(`${archive.url} has no image manifest in index.json.`);
|
|
1415
|
+
}
|
|
1416
|
+
const manifest = readOciJson(
|
|
1417
|
+
blobPath(layoutDir, manifestDigest)
|
|
1418
|
+
);
|
|
1419
|
+
if (!manifest.layers?.length) {
|
|
1420
|
+
throw new Error(`${archive.url} has an image manifest with no layers.`);
|
|
1421
|
+
}
|
|
1422
|
+
for (const layer of manifest.layers) {
|
|
1423
|
+
if (!layer.digest || !LAYER_MEDIA_TYPE_RE.test(layer.mediaType ?? "")) {
|
|
1424
|
+
throw new Error(
|
|
1425
|
+
`${archive.url} has an unsupported layer media type ${JSON.stringify(
|
|
1426
|
+
layer.mediaType
|
|
1427
|
+
)}.`
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
const layerPath = blobPath(layoutDir, layer.digest);
|
|
1431
|
+
const actual = (0, import_node_crypto2.createHash)("sha256").update(new Uint8Array((0, import_node_fs4.readFileSync)(layerPath))).digest("hex");
|
|
1432
|
+
if (`sha256:${actual}` !== layer.digest) {
|
|
1433
|
+
throw new Error(
|
|
1434
|
+
`${archive.url} layer ${layer.digest} hashes to sha256:${actual}.`
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
await (0, import_tar.extract)({
|
|
1438
|
+
file: layerPath,
|
|
1439
|
+
cwd: cnbDir,
|
|
1440
|
+
strict: true,
|
|
1441
|
+
filter: (_path, entry) => {
|
|
1442
|
+
if (!("path" in entry))
|
|
1443
|
+
return false;
|
|
1444
|
+
const match = entry.path.match(/^\/?cnb\/(buildpacks\/.*)$/);
|
|
1445
|
+
if (!match || match[1].split("/").includes("..")) {
|
|
1446
|
+
return false;
|
|
1447
|
+
}
|
|
1448
|
+
entry.path = match[1];
|
|
1449
|
+
if (entry.type === "Link" && entry.linkpath) {
|
|
1450
|
+
entry.linkpath = entry.linkpath.replace(/^\/?cnb\//, "");
|
|
1451
|
+
}
|
|
1452
|
+
return true;
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
} finally {
|
|
1457
|
+
(0, import_node_fs4.rmSync)(layoutDir, { recursive: true, force: true });
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
async function fetchDistribution(buildpack, cnbDir) {
|
|
1461
|
+
await Promise.all([
|
|
1462
|
+
fetchPinnedArchive(LIFECYCLE, cnbDir),
|
|
1463
|
+
...buildpack.buildpacks.map(
|
|
1464
|
+
(entry) => fetchBuildpackage(vercelBuildpackArtifact(entry), cnbDir)
|
|
1465
|
+
)
|
|
1466
|
+
]);
|
|
1467
|
+
for (const entry of buildpack.buildpacks) {
|
|
1468
|
+
const directory = (0, import_node_path4.join)(
|
|
1469
|
+
cnbDir,
|
|
1470
|
+
"buildpacks",
|
|
1471
|
+
entry.id.replace("/", "_"),
|
|
1472
|
+
entry.version
|
|
1473
|
+
);
|
|
1474
|
+
for (const file of ["buildpack.toml", "bin/detect", "bin/build"]) {
|
|
1475
|
+
if (!(0, import_node_fs4.statSync)((0, import_node_path4.join)(directory, file), { throwIfNoEntry: false })?.isFile()) {
|
|
1476
|
+
throw new Error(
|
|
1477
|
+
`Buildpack ${entry.id}@${entry.version} is missing ${file}.`
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
function creatorArgs(image, imageRef) {
|
|
1484
|
+
return [
|
|
1485
|
+
"-app=/workspace",
|
|
1486
|
+
`-order=${ORDER_FILE}`,
|
|
1487
|
+
"-skip-restore",
|
|
1488
|
+
`-run-image=${image.runImage}`,
|
|
1489
|
+
"-report=/platform-output/report.toml",
|
|
1490
|
+
imageRef
|
|
1491
|
+
];
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/buildpacks/lifecycle/buildah.ts
|
|
1495
|
+
var import_node_fs5 = require("fs");
|
|
1496
|
+
var import_node_crypto3 = require("crypto");
|
|
1497
|
+
var import_node_os5 = require("os");
|
|
1498
|
+
var import_node_path5 = require("path");
|
|
1499
|
+
async function runBuildah2(args, env) {
|
|
1500
|
+
const storageArgs = await buildahStorageArgs();
|
|
1501
|
+
return run("buildah", [...storageArgs, ...args], { env });
|
|
1502
|
+
}
|
|
1503
|
+
async function removeContainer(name) {
|
|
1504
|
+
try {
|
|
1505
|
+
await runBuildah2(["rm", name]);
|
|
1506
|
+
} catch (error) {
|
|
1507
|
+
debug(
|
|
1508
|
+
`could not remove Buildah CNB container ${name}: ${error.message}`
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
var buildAndPushWithLifecycle = async (buildpack, params, span) => {
|
|
1513
|
+
const urls = distributionUrls(buildpack);
|
|
1514
|
+
return withSpan(
|
|
1515
|
+
span,
|
|
1516
|
+
"container.buildpack.lifecycle_registry_build",
|
|
1517
|
+
{
|
|
1518
|
+
"buildpack.runtime": buildpack.runtime,
|
|
1519
|
+
"buildpack.runtime_version": params.image.version,
|
|
1520
|
+
"buildpack.build_image": params.image.buildImage,
|
|
1521
|
+
"buildpack.run_image": params.image.runImage,
|
|
1522
|
+
"buildpack.lifecycle_url": urls.lifecycle,
|
|
1523
|
+
"buildpack.archive_url": urls.buildpacks.join(","),
|
|
1524
|
+
"buildpack.node_version": params.nodeToolchain?.version,
|
|
1525
|
+
"image.ref": params.imageRef
|
|
1526
|
+
},
|
|
1527
|
+
async (lifecycleSpan) => {
|
|
1528
|
+
assertPublishedDistribution(buildpack);
|
|
1529
|
+
const suffix = `${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}`;
|
|
1530
|
+
const buildContainer = `vercel-cnb-${buildpack.runtime}-${suffix}`;
|
|
1531
|
+
let cnbDir;
|
|
1532
|
+
let reportDir;
|
|
1533
|
+
let orderDir;
|
|
1534
|
+
let platformEnvDir;
|
|
1535
|
+
try {
|
|
1536
|
+
reportDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)((0, import_node_os5.tmpdir)(), "vercel-cnb-report-"));
|
|
1537
|
+
const reportPath = (0, import_node_path5.join)(reportDir, "report.toml");
|
|
1538
|
+
(0, import_node_fs5.chownSync)(reportDir, BUILD_USER_ID, BUILD_GROUP_ID);
|
|
1539
|
+
(0, import_node_fs5.chmodSync)(reportDir, 493);
|
|
1540
|
+
cnbDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)((0, import_node_os5.tmpdir)(), "vercel-cnb-"));
|
|
1541
|
+
(0, import_node_fs5.chmodSync)(cnbDir, 493);
|
|
1542
|
+
orderDir = writeOrderDir(buildpack);
|
|
1543
|
+
platformEnvDir = writePlatformEnvDir(params.buildEnv, {
|
|
1544
|
+
nodeHome: params.nodeToolchain?.home
|
|
1545
|
+
});
|
|
1546
|
+
step("Fetching the CNB lifecycle and buildpacks...");
|
|
1547
|
+
await fetchDistribution(buildpack, cnbDir);
|
|
1548
|
+
done("fetched the CNB lifecycle and buildpacks");
|
|
1549
|
+
step(
|
|
1550
|
+
`Preparing ${buildpack.runtime} ${params.image.version} stock image and Vercel buildpacks`
|
|
1551
|
+
);
|
|
1552
|
+
await runBuildah2([
|
|
1553
|
+
"from",
|
|
1554
|
+
"--pull=always",
|
|
1555
|
+
"--platform",
|
|
1556
|
+
TARGET_PLATFORM,
|
|
1557
|
+
"--name",
|
|
1558
|
+
buildContainer,
|
|
1559
|
+
params.image.buildImage
|
|
1560
|
+
]);
|
|
1561
|
+
await runBuildah2([
|
|
1562
|
+
"run",
|
|
1563
|
+
"--network",
|
|
1564
|
+
"host",
|
|
1565
|
+
buildContainer,
|
|
1566
|
+
"--",
|
|
1567
|
+
"sh",
|
|
1568
|
+
"-c",
|
|
1569
|
+
`groupadd -g ${BUILD_GROUP_ID} cnb && useradd -m -u ${BUILD_USER_ID} -g ${BUILD_GROUP_ID} cnb && mkdir -p /layers /platform /workspace && chown -R ${BUILD_USER} /layers /platform /workspace /home/cnb`
|
|
1570
|
+
]);
|
|
1571
|
+
await runBuildah2([
|
|
1572
|
+
"copy",
|
|
1573
|
+
"--chown",
|
|
1574
|
+
BUILD_USER,
|
|
1575
|
+
buildContainer,
|
|
1576
|
+
params.workPath,
|
|
1577
|
+
"/workspace"
|
|
1578
|
+
]);
|
|
1579
|
+
const platformEnvMount = [
|
|
1580
|
+
"--volume",
|
|
1581
|
+
`${platformEnvDir}:/platform/env:ro`
|
|
1582
|
+
];
|
|
1583
|
+
const nodeToolchainMount = params.nodeToolchain ? [
|
|
1584
|
+
"--volume",
|
|
1585
|
+
`${params.nodeToolchain.home}:${params.nodeToolchain.home}:ro`
|
|
1586
|
+
] : [];
|
|
1587
|
+
const lifecycleEnv = {
|
|
1588
|
+
...process.env,
|
|
1589
|
+
CNB_REGISTRY_AUTH: cnbRegistryAuth(params.credentials)
|
|
1590
|
+
};
|
|
1591
|
+
step(
|
|
1592
|
+
`Building and publishing ${params.imageRef} via Vercel ${buildpack.runtime} buildpacks`
|
|
1593
|
+
);
|
|
1594
|
+
await runBuildah2(
|
|
1595
|
+
[
|
|
1596
|
+
"run",
|
|
1597
|
+
"--network",
|
|
1598
|
+
"host",
|
|
1599
|
+
"--user",
|
|
1600
|
+
BUILD_USER,
|
|
1601
|
+
"--env",
|
|
1602
|
+
"HOME=/home/cnb",
|
|
1603
|
+
"--env",
|
|
1604
|
+
`CNB_USER_ID=${BUILD_USER_ID}`,
|
|
1605
|
+
"--env",
|
|
1606
|
+
`CNB_GROUP_ID=${BUILD_GROUP_ID}`,
|
|
1607
|
+
"--env",
|
|
1608
|
+
`CNB_PLATFORM_API=${CNB_PLATFORM_API}`,
|
|
1609
|
+
"--env",
|
|
1610
|
+
"CNB_REGISTRY_AUTH",
|
|
1611
|
+
"--volume",
|
|
1612
|
+
`${cnbDir}:/cnb:ro`,
|
|
1613
|
+
"--volume",
|
|
1614
|
+
`${reportDir}:/platform-output`,
|
|
1615
|
+
"--volume",
|
|
1616
|
+
`${orderDir}:${ORDER_MOUNT_DIR}:ro`,
|
|
1617
|
+
...nodeToolchainMount,
|
|
1618
|
+
...platformEnvMount,
|
|
1619
|
+
buildContainer,
|
|
1620
|
+
"--",
|
|
1621
|
+
"/cnb/lifecycle/creator",
|
|
1622
|
+
...creatorArgs(params.image, params.imageRef)
|
|
1623
|
+
],
|
|
1624
|
+
lifecycleEnv
|
|
1625
|
+
);
|
|
1626
|
+
const digest = readReportDigest((0, import_node_fs5.readFileSync)(reportPath, "utf8"));
|
|
1627
|
+
if (!digest) {
|
|
1628
|
+
throw new Error(
|
|
1629
|
+
`${buildpack.runtime} buildpack lifecycle did not report a digest.`
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
done(`built and published ${params.imageRef}@${digest}`);
|
|
1633
|
+
lifecycleSpan?.setAttributes({ "image.digest": digest });
|
|
1634
|
+
return {
|
|
1635
|
+
imageRef: params.imageRef,
|
|
1636
|
+
digest,
|
|
1637
|
+
buildImage: params.image.buildImage,
|
|
1638
|
+
runImage: params.image.runImage,
|
|
1639
|
+
runtimeVersion: params.image.version
|
|
1640
|
+
};
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
const exitCode = error.exitCode;
|
|
1643
|
+
const hint = describeCreatorExitCode(exitCode);
|
|
1644
|
+
lifecycleSpan?.setAttributes({
|
|
1645
|
+
...exitCode !== void 0 ? { "buildpack.lifecycle.exit_code": String(exitCode) } : {},
|
|
1646
|
+
...hint ? { "buildpack.lifecycle.failure_phase": hint } : {}
|
|
1647
|
+
});
|
|
1648
|
+
throw new Error(
|
|
1649
|
+
[
|
|
1650
|
+
`${buildpack.runtime} buildpack build failed via lifecycle/creator (${params.image.buildImage}).`,
|
|
1651
|
+
`Lifecycle archive: ${urls.lifecycle}`,
|
|
1652
|
+
...urls.buildpacks.map((url) => `Buildpack archive: ${url}`),
|
|
1653
|
+
...hint ? [`The lifecycle exited with code ${exitCode}: ${hint}.`] : [],
|
|
1654
|
+
"",
|
|
1655
|
+
`Underlying error: ${error.message}`,
|
|
1656
|
+
"",
|
|
1657
|
+
`Buildpack project is ${params.workPath}`
|
|
1658
|
+
].join("\n")
|
|
1659
|
+
);
|
|
1660
|
+
} finally {
|
|
1661
|
+
await removeContainer(buildContainer);
|
|
1662
|
+
for (const dir of [cnbDir, reportDir, orderDir, platformEnvDir]) {
|
|
1663
|
+
if (dir) {
|
|
1664
|
+
try {
|
|
1665
|
+
(0, import_node_fs5.rmSync)(dir, { recursive: true, force: true });
|
|
1666
|
+
} catch {
|
|
1667
|
+
debug(`Could not remove Buildah build scratch directory ${dir}.`);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
);
|
|
1674
|
+
};
|
|
1675
|
+
|
|
1676
|
+
// src/buildpacks/lifecycle/docker.ts
|
|
1677
|
+
var import_node_crypto4 = require("crypto");
|
|
1678
|
+
var import_node_fs6 = require("fs");
|
|
1679
|
+
var import_node_os6 = require("os");
|
|
1680
|
+
var import_node_path6 = require("path");
|
|
1681
|
+
function localBuildImageDockerfile(buildImage, nodeMajor) {
|
|
1682
|
+
const lines = [];
|
|
1683
|
+
if (nodeMajor !== void 0) {
|
|
1684
|
+
lines.push(
|
|
1685
|
+
`FROM docker.io/library/node:${nodeMajor}-bookworm-slim AS node`
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
lines.push(`FROM ${buildImage}`);
|
|
1689
|
+
if (nodeMajor !== void 0) {
|
|
1690
|
+
lines.push(`COPY --from=node /usr/local /node${nodeMajor}`);
|
|
1691
|
+
}
|
|
1692
|
+
lines.push(
|
|
1693
|
+
`RUN groupadd --gid ${BUILD_GROUP_ID} cnb && useradd --create-home --uid ${BUILD_USER_ID} --gid ${BUILD_GROUP_ID} cnb && mkdir -p /layers /platform /workspace && chown -R ${BUILD_USER} /layers /workspace /home/cnb`
|
|
1694
|
+
);
|
|
1695
|
+
return `${lines.join("\n")}
|
|
1696
|
+
`;
|
|
1697
|
+
}
|
|
1698
|
+
function localBuildImageTag(dockerfile) {
|
|
1699
|
+
const hash = (0, import_node_crypto4.createHash)("sha256").update(dockerfile).digest("hex");
|
|
1700
|
+
return `vercel-cnb-build:${hash.slice(0, 16)}`;
|
|
1701
|
+
}
|
|
1702
|
+
async function runDocker2(args, opts = {}) {
|
|
1703
|
+
debug(`exec: docker ${args.join(" ")}`);
|
|
1704
|
+
return run("docker", args, opts);
|
|
1705
|
+
}
|
|
1706
|
+
var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
|
|
1707
|
+
const urls = distributionUrls(buildpack);
|
|
1708
|
+
return withSpan(
|
|
1709
|
+
span,
|
|
1710
|
+
"container.buildpack.lifecycle_docker_build",
|
|
1711
|
+
{
|
|
1712
|
+
"buildpack.runtime": buildpack.runtime,
|
|
1713
|
+
"buildpack.runtime_version": params.image.version,
|
|
1714
|
+
"buildpack.build_image": params.image.buildImage,
|
|
1715
|
+
"buildpack.run_image": params.image.runImage,
|
|
1716
|
+
"buildpack.lifecycle_url": urls.lifecycle,
|
|
1717
|
+
"buildpack.archive_url": urls.buildpacks.join(","),
|
|
1718
|
+
"buildpack.node_version": params.nodeToolchain?.version,
|
|
1719
|
+
"image.ref": params.imageRef
|
|
1720
|
+
},
|
|
1721
|
+
async (lifecycleSpan) => {
|
|
1722
|
+
assertPublishedDistribution(buildpack);
|
|
1723
|
+
let stageDir;
|
|
1724
|
+
const containers = [];
|
|
1725
|
+
try {
|
|
1726
|
+
stageDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path6.join)((0, import_node_os6.tmpdir)(), "vercel-cnb-"));
|
|
1727
|
+
const cnbDir = (0, import_node_path6.join)(stageDir, "cnb");
|
|
1728
|
+
(0, import_node_fs6.mkdirSync)(cnbDir, { mode: 493 });
|
|
1729
|
+
const orderDir = writeOrderDir(buildpack, stageDir);
|
|
1730
|
+
const platformEnvDir = writePlatformEnvDir(
|
|
1731
|
+
params.buildEnv,
|
|
1732
|
+
{ nodeHome: params.nodeToolchain?.home },
|
|
1733
|
+
stageDir
|
|
1734
|
+
);
|
|
1735
|
+
step("Fetching the CNB lifecycle and buildpacks...");
|
|
1736
|
+
await fetchDistribution(buildpack, cnbDir);
|
|
1737
|
+
done("fetched the CNB lifecycle and buildpacks");
|
|
1738
|
+
const dockerfile = localBuildImageDockerfile(
|
|
1739
|
+
params.image.buildImage,
|
|
1740
|
+
params.nodeToolchain?.major
|
|
1741
|
+
);
|
|
1742
|
+
const buildImageTag = localBuildImageTag(dockerfile);
|
|
1743
|
+
const buildImageIdPath = (0, import_node_path6.join)(stageDir, "build-image.id");
|
|
1744
|
+
step(
|
|
1745
|
+
`Preparing ${buildpack.runtime} ${params.image.version} build image` + (params.nodeToolchain ? ` with Node.js ${params.nodeToolchain.major}` : "")
|
|
1746
|
+
);
|
|
1747
|
+
await runDocker2(
|
|
1748
|
+
[
|
|
1749
|
+
"build",
|
|
1750
|
+
"--pull",
|
|
1751
|
+
"--platform",
|
|
1752
|
+
TARGET_PLATFORM,
|
|
1753
|
+
"--tag",
|
|
1754
|
+
buildImageTag,
|
|
1755
|
+
"--iidfile",
|
|
1756
|
+
buildImageIdPath,
|
|
1757
|
+
"-"
|
|
1758
|
+
],
|
|
1759
|
+
{ input: dockerfile }
|
|
1760
|
+
);
|
|
1761
|
+
const buildImageId = (0, import_node_fs6.readFileSync)(buildImageIdPath, "utf8").trim();
|
|
1762
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(buildImageId)) {
|
|
1763
|
+
throw new Error("Docker build did not report a valid image ID.");
|
|
1764
|
+
}
|
|
1765
|
+
const containerName = `vercel-cnb-${buildpack.runtime}-${process.pid}-${(0, import_node_crypto4.randomBytes)(8).toString("hex")}`;
|
|
1766
|
+
const initContainerName = `${containerName}-init`;
|
|
1767
|
+
containers.push(initContainerName);
|
|
1768
|
+
await runDocker2([
|
|
1769
|
+
"create",
|
|
1770
|
+
"--name",
|
|
1771
|
+
initContainerName,
|
|
1772
|
+
"--platform",
|
|
1773
|
+
TARGET_PLATFORM,
|
|
1774
|
+
"--user",
|
|
1775
|
+
"0:0",
|
|
1776
|
+
"--network",
|
|
1777
|
+
"none",
|
|
1778
|
+
"--volume",
|
|
1779
|
+
"/workspace",
|
|
1780
|
+
"--volume",
|
|
1781
|
+
"/layers",
|
|
1782
|
+
"--volume",
|
|
1783
|
+
"/platform-output",
|
|
1784
|
+
"--volume",
|
|
1785
|
+
"/cnb",
|
|
1786
|
+
"--volume",
|
|
1787
|
+
"/platform",
|
|
1788
|
+
"--entrypoint",
|
|
1789
|
+
"/bin/sh",
|
|
1790
|
+
buildImageId,
|
|
1791
|
+
"-c",
|
|
1792
|
+
`chown -R -h ${BUILD_USER} /workspace /layers /platform-output && chown -R -h 0:0 /cnb /platform`
|
|
1793
|
+
]);
|
|
1794
|
+
step("Copying the project into the build workspace");
|
|
1795
|
+
await runDocker2([
|
|
1796
|
+
"cp",
|
|
1797
|
+
`${params.workPath}/.`,
|
|
1798
|
+
`${initContainerName}:/workspace`
|
|
1799
|
+
]);
|
|
1800
|
+
for (const [source, destination] of [
|
|
1801
|
+
[`${cnbDir}/.`, "/cnb"],
|
|
1802
|
+
[orderDir, ORDER_MOUNT_DIR],
|
|
1803
|
+
[platformEnvDir, "/platform/env"]
|
|
1804
|
+
]) {
|
|
1805
|
+
await runDocker2([
|
|
1806
|
+
"cp",
|
|
1807
|
+
source,
|
|
1808
|
+
`${initContainerName}:${destination}`
|
|
1809
|
+
]);
|
|
1810
|
+
}
|
|
1811
|
+
await runDocker2(["start", "--attach", initContainerName]);
|
|
1812
|
+
const lifecycleEnv = {
|
|
1813
|
+
...process.env,
|
|
1814
|
+
CNB_REGISTRY_AUTH: cnbRegistryAuth(params.credentials)
|
|
1815
|
+
};
|
|
1816
|
+
containers.push(containerName);
|
|
1817
|
+
await runDocker2(
|
|
1818
|
+
[
|
|
1819
|
+
"create",
|
|
1820
|
+
"--name",
|
|
1821
|
+
containerName,
|
|
1822
|
+
"--platform",
|
|
1823
|
+
TARGET_PLATFORM,
|
|
1824
|
+
"--user",
|
|
1825
|
+
BUILD_USER,
|
|
1826
|
+
"--env",
|
|
1827
|
+
"HOME=/home/cnb",
|
|
1828
|
+
"--env",
|
|
1829
|
+
`CNB_USER_ID=${BUILD_USER_ID}`,
|
|
1830
|
+
"--env",
|
|
1831
|
+
`CNB_GROUP_ID=${BUILD_GROUP_ID}`,
|
|
1832
|
+
"--env",
|
|
1833
|
+
`CNB_PLATFORM_API=${CNB_PLATFORM_API}`,
|
|
1834
|
+
"--env",
|
|
1835
|
+
"CNB_REGISTRY_AUTH",
|
|
1836
|
+
"--volumes-from",
|
|
1837
|
+
initContainerName,
|
|
1838
|
+
"--entrypoint",
|
|
1839
|
+
"/cnb/lifecycle/creator",
|
|
1840
|
+
buildImageId,
|
|
1841
|
+
...creatorArgs(params.image, params.imageRef)
|
|
1842
|
+
],
|
|
1843
|
+
{ env: lifecycleEnv }
|
|
1844
|
+
);
|
|
1845
|
+
step(
|
|
1846
|
+
`Building and publishing ${params.imageRef} via Vercel ${buildpack.runtime} buildpacks`
|
|
1847
|
+
);
|
|
1848
|
+
await runDocker2(["start", "--attach", containerName]);
|
|
1849
|
+
const reportPath = (0, import_node_path6.join)(stageDir, "report.toml");
|
|
1850
|
+
await runDocker2([
|
|
1851
|
+
"cp",
|
|
1852
|
+
`${containerName}:/platform-output/report.toml`,
|
|
1853
|
+
reportPath
|
|
1854
|
+
]);
|
|
1855
|
+
const digest = readReportDigest((0, import_node_fs6.readFileSync)(reportPath, "utf8"));
|
|
1856
|
+
if (!digest) {
|
|
1857
|
+
throw new Error(
|
|
1858
|
+
`${buildpack.runtime} buildpack lifecycle did not report a digest.`
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
done(`built and published ${params.imageRef}@${digest}`);
|
|
1862
|
+
lifecycleSpan?.setAttributes({ "image.digest": digest });
|
|
1863
|
+
return {
|
|
1864
|
+
imageRef: params.imageRef,
|
|
1865
|
+
digest,
|
|
1866
|
+
buildImage: params.image.buildImage,
|
|
1867
|
+
runImage: params.image.runImage,
|
|
1868
|
+
runtimeVersion: params.image.version
|
|
1869
|
+
};
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
const exitCode = error.exitCode;
|
|
1872
|
+
const hint = describeCreatorExitCode(exitCode);
|
|
1873
|
+
lifecycleSpan?.setAttributes({
|
|
1874
|
+
...exitCode !== void 0 ? { "buildpack.lifecycle.exit_code": String(exitCode) } : {},
|
|
1875
|
+
...hint ? { "buildpack.lifecycle.failure_phase": hint } : {}
|
|
1876
|
+
});
|
|
1877
|
+
throw new Error(
|
|
1878
|
+
[
|
|
1879
|
+
`${buildpack.runtime} buildpack build failed via docker (${params.image.buildImage}).`,
|
|
1880
|
+
...hint ? [`The lifecycle exited with code ${exitCode}: ${hint}.`] : [],
|
|
1881
|
+
"",
|
|
1882
|
+
`Underlying error: ${error.message}`,
|
|
1883
|
+
"",
|
|
1884
|
+
`Buildpack project is ${params.workPath}`
|
|
1885
|
+
].join("\n")
|
|
1886
|
+
);
|
|
1887
|
+
} finally {
|
|
1888
|
+
for (const container of containers.reverse()) {
|
|
1889
|
+
try {
|
|
1890
|
+
await runDocker2(["rm", "--force", "--volumes", container], {
|
|
1891
|
+
quiet: true
|
|
1892
|
+
});
|
|
1893
|
+
} catch {
|
|
1894
|
+
debug(`Could not remove Docker build container ${container}.`);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
if (stageDir) {
|
|
1898
|
+
try {
|
|
1899
|
+
(0, import_node_fs6.rmSync)(stageDir, { recursive: true, force: true });
|
|
1900
|
+
} catch {
|
|
1901
|
+
debug(
|
|
1902
|
+
`Could not remove Docker build scratch directory ${stageDir}.`
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
);
|
|
1909
|
+
};
|
|
1910
|
+
|
|
1911
|
+
// src/buildpacks/node-toolchain.ts
|
|
1912
|
+
var import_build_utils4 = require("@vercel/build-utils");
|
|
1913
|
+
var import_node_fs7 = require("fs");
|
|
1914
|
+
var import_node_path7 = require("path");
|
|
1915
|
+
async function selectNodeMajor(workPath, config, meta) {
|
|
1916
|
+
if (!(0, import_node_fs7.existsSync)((0, import_node_path7.join)(workPath, "package.json"))) {
|
|
1917
|
+
return void 0;
|
|
1918
|
+
}
|
|
1919
|
+
const version2 = await (0, import_build_utils4.getNodeVersion)(workPath, void 0, config, meta);
|
|
1920
|
+
if ((0, import_build_utils4.isBunVersion)(version2)) {
|
|
1921
|
+
throw new Error(
|
|
1922
|
+
"Buildpack asset builds require Node.js. Bun is not supported yet; configure engines.node or use Dockerfile.vercel."
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
return {
|
|
1926
|
+
home: `/node${version2.major}`,
|
|
1927
|
+
version: version2.range,
|
|
1928
|
+
major: version2.major
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
async function resolveNodeToolchain(workPath, config, meta) {
|
|
1932
|
+
const toolchain = await selectNodeMajor(workPath, config, meta);
|
|
1933
|
+
if (!toolchain) {
|
|
1934
|
+
return void 0;
|
|
1935
|
+
}
|
|
1936
|
+
for (const executable of ["node", "npm"]) {
|
|
1937
|
+
if (!(0, import_node_fs7.existsSync)((0, import_node_path7.join)(toolchain.home, "bin", executable))) {
|
|
1938
|
+
throw new Error(
|
|
1939
|
+
`The selected Node.js ${toolchain.version} installation is missing ${toolchain.home}/bin/${executable} in the build environment.`
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
return toolchain;
|
|
1944
|
+
}
|
|
1945
|
+
async function resolveLocalNodeToolchain(workPath, config, meta) {
|
|
1946
|
+
return selectNodeMajor(workPath, config, meta);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// src/buildpacks/registry.ts
|
|
1950
|
+
var import_node_fs9 = require("fs");
|
|
1951
|
+
var import_node_path9 = require("path");
|
|
1952
|
+
|
|
1953
|
+
// src/buildpacks/ruby.ts
|
|
1954
|
+
var import_node_fs8 = require("fs");
|
|
1955
|
+
var import_node_path8 = require("path");
|
|
1956
|
+
var import_smol_toml2 = require("smol-toml");
|
|
1957
|
+
var DEFAULT_RUBY_VERSION = "3.4";
|
|
1958
|
+
var RUBY_IMAGE_REPOSITORY = "docker.io/library/ruby";
|
|
1959
|
+
var RUBY_VERSION_DOCS = "https://vercel.link/ruby-version";
|
|
1960
|
+
var RUBY_VERSION_FILES = [
|
|
1961
|
+
".ruby-version",
|
|
1962
|
+
".tool-versions",
|
|
1963
|
+
"mise.toml"
|
|
1964
|
+
];
|
|
1965
|
+
var VERSION_FILE_RE = /^ruby[\s-]*(?:=\s*)?["']?([^\s#"']+)["']?/m;
|
|
1966
|
+
var VERSION_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?$/;
|
|
1967
|
+
var GEMFILE_RUBY_CALL_RE = /^\s*ruby\b(?!\s*:)/;
|
|
1968
|
+
var GEMFILE_RUBY_LINE_RE = /^\s*ruby(?:\s*\(\s*("[^"]*"|'[^']*')\s*\)|\s*("[^"]*"|'[^']*'))\s*(?:#.*)?$/;
|
|
1969
|
+
var ruby = {
|
|
1970
|
+
runtime: "ruby",
|
|
1971
|
+
projectMarkers: ["Gemfile"],
|
|
1972
|
+
buildpacks: [vercelBuildpack("ruby")],
|
|
1973
|
+
async resolveImage(workPath) {
|
|
1974
|
+
const version2 = selectRubyVersion(workPath) ?? DEFAULT_RUBY_VERSION;
|
|
1975
|
+
const image = `${RUBY_IMAGE_REPOSITORY}:${version2}`;
|
|
1976
|
+
return { version: version2, buildImage: image, runImage: image };
|
|
1977
|
+
}
|
|
1978
|
+
};
|
|
1979
|
+
function isValidVersion(version2) {
|
|
1980
|
+
return VERSION_RE.test(version2) && version2.split(".").every((n) => Number.isSafeInteger(+n));
|
|
1981
|
+
}
|
|
1982
|
+
function selectRubyVersion(workPath) {
|
|
1983
|
+
for (const name of RUBY_VERSION_FILES) {
|
|
1984
|
+
const file = (0, import_node_path8.join)(workPath, name);
|
|
1985
|
+
if ((0, import_node_fs8.existsSync)(file)) {
|
|
1986
|
+
return parseVersionFile(name, (0, import_node_fs8.readFileSync)(file, "utf8"));
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
const gemfile = (0, import_node_path8.join)(workPath, "Gemfile");
|
|
1990
|
+
return (0, import_node_fs8.existsSync)(gemfile) ? parseGemfileRubyVersion((0, import_node_fs8.readFileSync)(gemfile, "utf8")) : void 0;
|
|
1991
|
+
}
|
|
1992
|
+
function parseVersionFile(name, contents) {
|
|
1993
|
+
let version2;
|
|
1994
|
+
if (name === "mise.toml") {
|
|
1995
|
+
try {
|
|
1996
|
+
const { tools } = (0, import_smol_toml2.parse)(contents);
|
|
1997
|
+
version2 = typeof tools === "object" && "ruby" in tools ? tools.ruby : void 0;
|
|
1998
|
+
} catch (error) {
|
|
1999
|
+
throw new Error(`Invalid ${name}: ${error.message}`);
|
|
2000
|
+
}
|
|
2001
|
+
} else {
|
|
2002
|
+
version2 = VERSION_FILE_RE.exec(contents)?.[1] ?? contents.trim();
|
|
2003
|
+
}
|
|
2004
|
+
if (typeof version2 !== "string" || !isValidVersion(version2)) {
|
|
2005
|
+
throw new Error(
|
|
2006
|
+
`Invalid Ruby version in ${name}: ${JSON.stringify(version2)}. Expected a numeric major.minor or major.minor.patch version such as 3.4.1; only MRI is supported. Learn more: ${RUBY_VERSION_DOCS}`
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
return version2;
|
|
2010
|
+
}
|
|
2011
|
+
function parseGemfileRubyVersion(contents) {
|
|
2012
|
+
const lines = contents.split(/\r?\n/).filter((line2) => GEMFILE_RUBY_CALL_RE.test(line2));
|
|
2013
|
+
if (lines.length === 0)
|
|
2014
|
+
return void 0;
|
|
2015
|
+
const [line] = lines;
|
|
2016
|
+
const match = lines.length === 1 ? GEMFILE_RUBY_LINE_RE.exec(line) : null;
|
|
2017
|
+
const version2 = (match?.[1] ?? match?.[2])?.slice(1, -1);
|
|
2018
|
+
if (version2 === void 0 || !isValidVersion(version2)) {
|
|
2019
|
+
throw new Error(
|
|
2020
|
+
`Unsupported ruby declaration in Gemfile: ${JSON.stringify(line.trim())}. Pin Ruby with a .ruby-version, .tool-versions, or mise.toml file, or declare an exact version such as ruby "3.4.1". Version operators, engine:, patchlevel:, file:, and computed values are not supported. Learn more: ${RUBY_VERSION_DOCS}`
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
return [...version2.split("."), "0"].slice(0, 3).join(".");
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
// src/buildpacks/registry.ts
|
|
2027
|
+
var BUILDPACKS = [ruby];
|
|
2028
|
+
function requestedBuildpack(config) {
|
|
2029
|
+
if (!config)
|
|
2030
|
+
return void 0;
|
|
2031
|
+
return BUILDPACKS.find((bp) => bp.runtime === config.buildpack);
|
|
2032
|
+
}
|
|
2033
|
+
function hasProjectMarkers(buildpack, workPath) {
|
|
2034
|
+
return buildpack.projectMarkers.some(
|
|
2035
|
+
(name) => (0, import_node_fs9.existsSync)((0, import_node_path9.join)(workPath, name))
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// src/buildpacks/build.ts
|
|
2040
|
+
async function buildAndPushBuildpack(params) {
|
|
2041
|
+
const sourceDir = (0, import_node_fs10.mkdtempSync)((0, import_node_path10.join)((0, import_node_os7.tmpdir)(), "vercel-cnb-source-"));
|
|
2042
|
+
try {
|
|
2043
|
+
step("Staging buildpack source files");
|
|
2044
|
+
await stageWorkspace(params, sourceDir);
|
|
2045
|
+
const engine = selectContainerEngine();
|
|
2046
|
+
if (!hasProjectMarkers(params.buildpack, sourceDir)) {
|
|
2047
|
+
throw new Error(
|
|
2048
|
+
`The ${params.buildpack.runtime} buildpack was selected, but no supported project marker was found in the service sources. Add ${params.buildpack.projectMarkers.join(" or ")}, or add a Dockerfile.vercel to control the image build.`
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
const image = await params.buildpack.resolveImage(sourceDir);
|
|
2052
|
+
const nodeToolchain = engine.name === "buildah" ? await resolveNodeToolchain(sourceDir, params.config, params.meta) : await resolveLocalNodeToolchain(
|
|
2053
|
+
sourceDir,
|
|
2054
|
+
params.config,
|
|
2055
|
+
params.meta
|
|
2056
|
+
);
|
|
2057
|
+
return await withSpan(
|
|
2058
|
+
params.parentSpan,
|
|
2059
|
+
"container.buildpack.build_and_push",
|
|
2060
|
+
{
|
|
2061
|
+
"buildpack.runtime": params.buildpack.runtime,
|
|
2062
|
+
"buildpack.runtime_version": image.version,
|
|
2063
|
+
"buildpack.node_version": nodeToolchain?.version,
|
|
2064
|
+
"container.engine": engine.name,
|
|
2065
|
+
"container.repository": params.repository
|
|
2066
|
+
},
|
|
2067
|
+
async (buildSpan) => {
|
|
2068
|
+
const target = await resolveRegistryTarget({
|
|
2069
|
+
repository: params.repository,
|
|
2070
|
+
tag: params.tag,
|
|
2071
|
+
span: buildSpan
|
|
2072
|
+
});
|
|
2073
|
+
buildSpan?.setAttributes({
|
|
2074
|
+
"container.repository": target.fullRepository,
|
|
2075
|
+
"image.tag": params.tag,
|
|
2076
|
+
"image.ref": target.imageRef,
|
|
2077
|
+
"registry.username": target.username
|
|
2078
|
+
});
|
|
2079
|
+
return engine.withRuntime(buildSpan, async () => {
|
|
2080
|
+
await withSpan(
|
|
2081
|
+
buildSpan,
|
|
2082
|
+
"container.ensure_toolchain_ready",
|
|
2083
|
+
{ "container.engine": engine.name },
|
|
2084
|
+
(s) => engine.ensureReady(s)
|
|
2085
|
+
);
|
|
2086
|
+
await withSpan(
|
|
2087
|
+
buildSpan,
|
|
2088
|
+
"container.verify_storage",
|
|
2089
|
+
{ "container.engine": engine.name },
|
|
2090
|
+
(s) => engine.verifyStorage?.(s) ?? Promise.resolve()
|
|
2091
|
+
);
|
|
2092
|
+
const credentials = {
|
|
2093
|
+
registry: VCR_REGISTRY,
|
|
2094
|
+
username: target.username,
|
|
2095
|
+
token: target.token
|
|
2096
|
+
};
|
|
2097
|
+
step(`Authenticating to ${VCR_REGISTRY} as ${target.username}`);
|
|
2098
|
+
await withSpan(
|
|
2099
|
+
buildSpan,
|
|
2100
|
+
"container.registry_login",
|
|
2101
|
+
{
|
|
2102
|
+
"container.registry": VCR_REGISTRY,
|
|
2103
|
+
"registry.username": target.username
|
|
2104
|
+
},
|
|
2105
|
+
() => engine.login(credentials)
|
|
2106
|
+
);
|
|
2107
|
+
done("authenticated");
|
|
2108
|
+
await withSpan(
|
|
2109
|
+
buildSpan,
|
|
2110
|
+
"container.ensure_repository",
|
|
2111
|
+
{ "container.repository": params.repository },
|
|
2112
|
+
(s) => ensureRepository(
|
|
2113
|
+
params.repository,
|
|
2114
|
+
target.token,
|
|
2115
|
+
target.claims,
|
|
2116
|
+
s
|
|
2117
|
+
)
|
|
2118
|
+
);
|
|
2119
|
+
const runLifecycle = engine.name === "buildah" ? buildAndPushWithLifecycle : buildAndPushWithLifecycleDocker;
|
|
2120
|
+
const result = await runLifecycle(
|
|
2121
|
+
params.buildpack,
|
|
2122
|
+
{
|
|
2123
|
+
workPath: sourceDir,
|
|
2124
|
+
image,
|
|
2125
|
+
imageRef: target.imageRef,
|
|
2126
|
+
credentials,
|
|
2127
|
+
buildEnv: params.buildEnv,
|
|
2128
|
+
nodeToolchain
|
|
2129
|
+
},
|
|
2130
|
+
buildSpan
|
|
2131
|
+
);
|
|
2132
|
+
const resolvedRef = `${VCR_REGISTRY}/${target.fullRepository}@${result.digest}`;
|
|
2133
|
+
buildSpan?.setAttributes({
|
|
2134
|
+
"image.digest": result.digest,
|
|
2135
|
+
"image.resolved_ref": resolvedRef
|
|
2136
|
+
});
|
|
2137
|
+
info(`Image reference ${resolvedRef}`);
|
|
2138
|
+
return resolvedRef;
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
);
|
|
2142
|
+
} finally {
|
|
2143
|
+
try {
|
|
2144
|
+
(0, import_node_fs10.rmSync)(sourceDir, { recursive: true, force: true });
|
|
2145
|
+
} catch {
|
|
2146
|
+
debug(`Could not remove buildpack source snapshot ${sourceDir}.`);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
// src/image-source.ts
|
|
2152
|
+
var import_node_fs11 = require("fs");
|
|
2153
|
+
var import_node_path11 = __toESM(require("path"));
|
|
2154
|
+
var DETECT_SENTINEL = "<detect>";
|
|
2155
|
+
function resolveImageSource(options, context) {
|
|
2156
|
+
const { config, workPath, entrypoint } = options;
|
|
2157
|
+
const entrypointRef = readString(entrypoint);
|
|
2158
|
+
const isDetectSentinel = entrypointRef === DETECT_SENTINEL;
|
|
2159
|
+
const buildpack = requestedBuildpack(config);
|
|
2160
|
+
if (buildpack && !isDetectSentinel) {
|
|
2161
|
+
throw new Error(
|
|
2162
|
+
"Buildpack builds are selected by Vercel from the project's framework or the service's runtime; `buildpack` is not a `builds` configuration option."
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
|
|
2166
|
+
const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
|
|
2167
|
+
const dockerfilePath = import_node_path11.default.join(workPath, dockerfileRel);
|
|
2168
|
+
const hasDockerfile = dockerfileConfigured !== void 0 || !buildpack && (0, import_node_fs11.existsSync)(dockerfilePath);
|
|
2169
|
+
if (hasDockerfile) {
|
|
2170
|
+
return { kind: "dockerfile", dockerfileRel, dockerfilePath };
|
|
2171
|
+
}
|
|
2172
|
+
const prebuiltImage = readString(config.handler) ?? (isDetectSentinel ? void 0 : entrypointRef);
|
|
2173
|
+
if (prebuiltImage) {
|
|
2174
|
+
return { kind: "prebuilt", imageRef: prebuiltImage };
|
|
2175
|
+
}
|
|
2176
|
+
if (buildpack) {
|
|
2177
|
+
return { kind: "buildpack", buildpack };
|
|
2178
|
+
}
|
|
2179
|
+
throw new Error(
|
|
2180
|
+
"Container service must specify an entrypoint: a prebuilt OCI image reference, or a Dockerfile path to " + (context === "dev" ? "run with `vercel dev`." : "build.")
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
// src/dev.ts
|
|
2185
|
+
var import_node_child_process3 = require("child_process");
|
|
2186
|
+
var import_node_fs12 = require("fs");
|
|
2187
|
+
var import_node_os8 = require("os");
|
|
2188
|
+
var import_node_path12 = __toESM(require("path"));
|
|
2189
|
+
var HOST_ONLY_ENV = /* @__PURE__ */ new Set([
|
|
2190
|
+
"TMPDIR",
|
|
2191
|
+
"TMP",
|
|
2192
|
+
"TEMP",
|
|
2193
|
+
"HOME",
|
|
2194
|
+
"PATH",
|
|
2195
|
+
"PWD",
|
|
2196
|
+
"OLDPWD",
|
|
2197
|
+
"SHELL",
|
|
2198
|
+
"SHLVL",
|
|
2199
|
+
"USER",
|
|
2200
|
+
"LOGNAME",
|
|
2201
|
+
"TERM",
|
|
2202
|
+
"TERM_PROGRAM",
|
|
2203
|
+
"TERM_PROGRAM_VERSION",
|
|
2204
|
+
"TERM_SESSION_ID",
|
|
2205
|
+
"COLORTERM",
|
|
2206
|
+
"LANG",
|
|
2207
|
+
"LC_ALL",
|
|
2208
|
+
"LC_CTYPE",
|
|
2209
|
+
"COMMAND_MODE",
|
|
2210
|
+
"SECURITYSESSIONID",
|
|
2211
|
+
"__CF_USER_TEXT_ENCODING",
|
|
2212
|
+
"__CFBundleIdentifier"
|
|
2213
|
+
]);
|
|
2214
|
+
function isHostOnlyEnvVar(key) {
|
|
2215
|
+
return HOST_ONLY_ENV.has(key) || key.startsWith("__") || key.startsWith("XPC_") || key.startsWith("SSH_") || key.startsWith("Apple");
|
|
2216
|
+
}
|
|
2217
|
+
function writeEnvFile(env) {
|
|
2218
|
+
const dir = (0, import_node_fs12.mkdtempSync)(import_node_path12.default.join((0, import_node_os8.tmpdir)(), "vercel-container-dev-env-"));
|
|
2219
|
+
const file = import_node_path12.default.join(dir, "env");
|
|
2220
|
+
const lines = [];
|
|
2221
|
+
for (const [key, value] of Object.entries(env)) {
|
|
2222
|
+
if (value.includes("\n")) {
|
|
2223
|
+
continue;
|
|
2224
|
+
}
|
|
2225
|
+
lines.push(`${key}=${value}`);
|
|
2226
|
+
}
|
|
2227
|
+
(0, import_node_fs12.writeFileSync)(file, `${lines.join("\n")}
|
|
2228
|
+
`);
|
|
2229
|
+
return file;
|
|
2230
|
+
}
|
|
2231
|
+
function useContainerHost(endpoint) {
|
|
2232
|
+
try {
|
|
2233
|
+
const url = new URL(endpoint);
|
|
2234
|
+
if (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]") {
|
|
2235
|
+
url.hostname = "host.docker.internal";
|
|
2236
|
+
return url.toString();
|
|
2237
|
+
}
|
|
2238
|
+
} catch {
|
|
2239
|
+
}
|
|
2240
|
+
return endpoint;
|
|
2241
|
+
}
|
|
2242
|
+
function emit(out, line) {
|
|
2243
|
+
if (out.onStderr) {
|
|
2244
|
+
out.onStderr(Buffer.from(`${line}
|
|
2245
|
+
`));
|
|
2246
|
+
} else {
|
|
2247
|
+
process.stderr.write(`${line}
|
|
2248
|
+
`);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
function runForwarded(cmd, args, out, opts = {}) {
|
|
2252
|
+
return new Promise((resolve2, reject) => {
|
|
2253
|
+
const child = (0, import_node_child_process3.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
2254
|
+
let stdout = "";
|
|
2255
|
+
let stderr = "";
|
|
2256
|
+
child.stdout?.on("data", (chunk) => {
|
|
2257
|
+
stdout += chunk.toString();
|
|
2258
|
+
if (!opts.quiet) {
|
|
2259
|
+
if (out.onStdout) {
|
|
2260
|
+
out.onStdout(chunk);
|
|
2261
|
+
} else {
|
|
2262
|
+
process.stderr.write(chunk.toString());
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
});
|
|
2266
|
+
child.stderr?.on("data", (chunk) => {
|
|
2267
|
+
stderr += chunk.toString();
|
|
2268
|
+
if (!opts.quiet) {
|
|
2269
|
+
if (out.onStderr) {
|
|
2270
|
+
out.onStderr(chunk);
|
|
2271
|
+
} else {
|
|
2272
|
+
process.stderr.write(chunk.toString());
|
|
1169
2273
|
}
|
|
1170
2274
|
}
|
|
1171
2275
|
});
|
|
@@ -1182,7 +2286,7 @@ function runForwarded(cmd, args, out, opts = {}) {
|
|
|
1182
2286
|
});
|
|
1183
2287
|
child.on("close", (code) => {
|
|
1184
2288
|
if (code === 0) {
|
|
1185
|
-
|
|
2289
|
+
resolve2({ stdout });
|
|
1186
2290
|
} else {
|
|
1187
2291
|
const detail = stderr.trim().split("\n").slice(-5).join("\n");
|
|
1188
2292
|
reject(
|
|
@@ -1195,41 +2299,30 @@ ${detail}` : "")
|
|
|
1195
2299
|
});
|
|
1196
2300
|
});
|
|
1197
2301
|
}
|
|
1198
|
-
function normalizeCommand(command) {
|
|
1199
|
-
if (typeof command === "string") {
|
|
1200
|
-
return [command];
|
|
1201
|
-
}
|
|
1202
|
-
if (Array.isArray(command) && command.every((item) => typeof item === "string")) {
|
|
1203
|
-
return command;
|
|
1204
|
-
}
|
|
1205
|
-
return void 0;
|
|
1206
|
-
}
|
|
1207
2302
|
async function resolveDevImage(options, out, span) {
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
|
|
1211
|
-
const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
|
|
1212
|
-
const dockerfilePath = import_node_path4.default.join(workPath, dockerfileRel);
|
|
1213
|
-
const hasDockerfile = dockerfileConfigured !== void 0 || (0, import_node_fs4.existsSync)(dockerfilePath);
|
|
1214
|
-
const prebuiltImage = readString(config.handler) ?? (hasDockerfile ? void 0 : entrypointRef);
|
|
1215
|
-
if (!hasDockerfile) {
|
|
1216
|
-
if (!prebuiltImage) {
|
|
1217
|
-
throw new Error(
|
|
1218
|
-
"Container service must specify an entrypoint: a prebuilt OCI image reference, or a Dockerfile path to run with `vercel dev`."
|
|
1219
|
-
);
|
|
1220
|
-
}
|
|
2303
|
+
const source = resolveImageSource(options, "dev");
|
|
2304
|
+
if (source.kind === "prebuilt") {
|
|
1221
2305
|
span?.setAttributes({ "container.dev_mode": "prebuilt" });
|
|
1222
|
-
emit(
|
|
1223
|
-
|
|
2306
|
+
emit(
|
|
2307
|
+
out,
|
|
2308
|
+
`\u25B2 container vercel dev: using prebuilt image ${source.imageRef}`
|
|
2309
|
+
);
|
|
2310
|
+
return { image: source.imageRef };
|
|
2311
|
+
}
|
|
2312
|
+
if (source.kind === "buildpack") {
|
|
2313
|
+
throw new Error(
|
|
2314
|
+
`Unexpected buildpack image source in \`vercel dev\` for "${options.workPath}".`
|
|
2315
|
+
);
|
|
1224
2316
|
}
|
|
1225
|
-
|
|
2317
|
+
const { dockerfilePath } = source;
|
|
2318
|
+
if (!(0, import_node_fs12.existsSync)(dockerfilePath)) {
|
|
1226
2319
|
throw new Error(
|
|
1227
2320
|
`Dockerfile not found at "${dockerfilePath}" for container service.`
|
|
1228
2321
|
);
|
|
1229
2322
|
}
|
|
1230
2323
|
const serviceName = options.service?.name ?? "service";
|
|
1231
2324
|
const tag = devImageTag(serviceName);
|
|
1232
|
-
const contextDir =
|
|
2325
|
+
const contextDir = import_node_path12.default.dirname(dockerfilePath);
|
|
1233
2326
|
const buildArgFlags2 = [];
|
|
1234
2327
|
const buildEnv = options.meta?.buildEnv ?? {};
|
|
1235
2328
|
for (const [key, value] of Object.entries(buildEnv)) {
|
|
@@ -1245,7 +2338,7 @@ async function resolveDevImage(options, out, span) {
|
|
|
1245
2338
|
out
|
|
1246
2339
|
);
|
|
1247
2340
|
emit(out, `\u25B2 container built ${tag}`);
|
|
1248
|
-
return tag;
|
|
2341
|
+
return { image: tag };
|
|
1249
2342
|
}
|
|
1250
2343
|
async function resolveContainerPort(image, out) {
|
|
1251
2344
|
try {
|
|
@@ -1355,7 +2448,7 @@ async function startContainer(options, reuseKey) {
|
|
|
1355
2448
|
const { config, meta, onStdout, onStderr } = options;
|
|
1356
2449
|
const out = { onStdout, onStderr };
|
|
1357
2450
|
await assertDockerAvailable(out);
|
|
1358
|
-
const image = await withSpan(
|
|
2451
|
+
const { image } = await withSpan(
|
|
1359
2452
|
span,
|
|
1360
2453
|
"container.dev.resolve_image",
|
|
1361
2454
|
{},
|
|
@@ -1385,9 +2478,8 @@ async function startContainer(options, reuseKey) {
|
|
|
1385
2478
|
}
|
|
1386
2479
|
mergedEnv.PORT = String(containerPort);
|
|
1387
2480
|
const envFilePath = writeEnvFile(mergedEnv);
|
|
1388
|
-
const
|
|
1389
|
-
|
|
1390
|
-
);
|
|
2481
|
+
const rawCommand = config.command;
|
|
2482
|
+
const command = normalizeCommand(rawCommand);
|
|
1391
2483
|
const requestedHostPort = typeof meta?.port === "number" ? meta.port : 0;
|
|
1392
2484
|
const args = [
|
|
1393
2485
|
"run",
|
|
@@ -1421,7 +2513,7 @@ async function startContainer(options, reuseKey) {
|
|
|
1421
2513
|
}
|
|
1422
2514
|
});
|
|
1423
2515
|
const cleanupEnvFile = () => {
|
|
1424
|
-
(0,
|
|
2516
|
+
(0, import_node_fs12.rmSync)(import_node_path12.default.dirname(envFilePath), { recursive: true, force: true });
|
|
1425
2517
|
};
|
|
1426
2518
|
const shutdown = async () => {
|
|
1427
2519
|
runningContainers.delete(reuseKey);
|
|
@@ -1454,7 +2546,7 @@ async function startContainer(options, reuseKey) {
|
|
|
1454
2546
|
break;
|
|
1455
2547
|
} catch (err) {
|
|
1456
2548
|
lastErr = err;
|
|
1457
|
-
await new Promise((
|
|
2549
|
+
await new Promise((resolve2) => setTimeout(resolve2, 250));
|
|
1458
2550
|
}
|
|
1459
2551
|
}
|
|
1460
2552
|
if (hostPort === void 0) {
|
|
@@ -1497,11 +2589,11 @@ async function startContainer(options, reuseKey) {
|
|
|
1497
2589
|
}
|
|
1498
2590
|
|
|
1499
2591
|
// src/prepare-cache.ts
|
|
1500
|
-
var
|
|
1501
|
-
var
|
|
1502
|
-
var
|
|
2592
|
+
var import_build_utils5 = require("@vercel/build-utils");
|
|
2593
|
+
var import_node_fs13 = require("fs");
|
|
2594
|
+
var import_node_path13 = require("path");
|
|
1503
2595
|
var CACHE_ROOT = "/vercel";
|
|
1504
|
-
var GRAPH_ROOT_REL =
|
|
2596
|
+
var GRAPH_ROOT_REL = import_node_path13.posix.relative(CACHE_ROOT, BUILDAH_GRAPH_ROOT);
|
|
1505
2597
|
async function prepareCache(_options) {
|
|
1506
2598
|
if (process.env.VERCEL_VCR_DISABLE_LAYER_CACHE) {
|
|
1507
2599
|
debug("layer cache disabled (VERCEL_VCR_DISABLE_LAYER_CACHE)");
|
|
@@ -1511,12 +2603,12 @@ async function prepareCache(_options) {
|
|
|
1511
2603
|
debug("skipping container layer cache (not in build container)");
|
|
1512
2604
|
return {};
|
|
1513
2605
|
}
|
|
1514
|
-
if (!(0,
|
|
2606
|
+
if (!(0, import_node_fs13.existsSync)(BUILDAH_GRAPH_ROOT)) {
|
|
1515
2607
|
debug(`no buildah store to cache at ${BUILDAH_GRAPH_ROOT}`);
|
|
1516
2608
|
return {};
|
|
1517
2609
|
}
|
|
1518
2610
|
const start = Date.now();
|
|
1519
|
-
const files = await (0,
|
|
2611
|
+
const files = await (0, import_build_utils5.glob)(`${GRAPH_ROOT_REL}/**`, CACHE_ROOT);
|
|
1520
2612
|
const count = Object.keys(files).length;
|
|
1521
2613
|
info(
|
|
1522
2614
|
`cached container layer store: ${count} files from ${BUILDAH_GRAPH_ROOT} in ${Date.now() - start}ms`
|
|
@@ -1534,15 +2626,6 @@ function resolveFunctionSourceFile(options) {
|
|
|
1534
2626
|
}
|
|
1535
2627
|
return entrypoint;
|
|
1536
2628
|
}
|
|
1537
|
-
function normalizeCommand2(command) {
|
|
1538
|
-
if (typeof command === "string") {
|
|
1539
|
-
return [command];
|
|
1540
|
-
}
|
|
1541
|
-
if (Array.isArray(command) && command.every((item) => typeof item === "string")) {
|
|
1542
|
-
return command;
|
|
1543
|
-
}
|
|
1544
|
-
return void 0;
|
|
1545
|
-
}
|
|
1546
2629
|
function sanitizeRepository(name) {
|
|
1547
2630
|
const sanitized = name.toLowerCase().replace(/[^a-z0-9-_./]/g, "-").replace(/-+/g, "-").replace(/(^[-/.]+)|([-/.]+$)/g, "");
|
|
1548
2631
|
return sanitized || "service";
|
|
@@ -1558,6 +2641,33 @@ function resolveImageTag() {
|
|
|
1558
2641
|
}
|
|
1559
2642
|
return `build-${Date.now().toString(36)}`;
|
|
1560
2643
|
}
|
|
2644
|
+
async function authenticateRegistry(engine, buildParams, span) {
|
|
2645
|
+
const forceLogin = readString(process.env.VERCEL_VCR_FORCE_LOGIN) === "1";
|
|
2646
|
+
const authFile = forceLogin ? void 0 : existingRegistryAuthFile();
|
|
2647
|
+
if (authFile) {
|
|
2648
|
+
debug(`registry auth file present: ${authFile}`);
|
|
2649
|
+
step(`Using registry credentials from ${authFile}`);
|
|
2650
|
+
span?.setAttributes({
|
|
2651
|
+
"container.registry": VCR_REGISTRY,
|
|
2652
|
+
"registry.username": buildParams.username,
|
|
2653
|
+
"registry.auth_file": authFile,
|
|
2654
|
+
"registry.login_skipped": toTag(true)
|
|
2655
|
+
});
|
|
2656
|
+
done("authenticated via provisioned credentials");
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
step(`Authenticating to ${VCR_REGISTRY} as ${buildParams.username}`);
|
|
2660
|
+
await withSpan(
|
|
2661
|
+
span,
|
|
2662
|
+
"container.registry_login",
|
|
2663
|
+
{
|
|
2664
|
+
"container.registry": VCR_REGISTRY,
|
|
2665
|
+
"registry.username": buildParams.username
|
|
2666
|
+
},
|
|
2667
|
+
() => engine.login(buildParams)
|
|
2668
|
+
);
|
|
2669
|
+
done("authenticated");
|
|
2670
|
+
}
|
|
1561
2671
|
async function buildAndPushImage(params) {
|
|
1562
2672
|
const { contextDir, dockerfilePath, repository, tag, buildArgs, parentSpan } = params;
|
|
1563
2673
|
const engine = selectContainerEngine();
|
|
@@ -1570,25 +2680,12 @@ async function buildAndPushImage(params) {
|
|
|
1570
2680
|
"container.repository": repository
|
|
1571
2681
|
},
|
|
1572
2682
|
async (buildSpan) => {
|
|
1573
|
-
const
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
const claims = decodeOidcClaims(token);
|
|
1580
|
-
debug(`registry token: ${tokenFingerprint(token)}`);
|
|
1581
|
-
debugTokenClaims("OIDC token claims", token);
|
|
1582
|
-
const username = claims.owner_id;
|
|
1583
|
-
if (!username) {
|
|
1584
|
-
throw new Error(
|
|
1585
|
-
"VERCEL_OIDC_TOKEN is missing the `owner_id` (team id) claim required to authenticate to the container registry."
|
|
1586
|
-
);
|
|
1587
|
-
}
|
|
1588
|
-
const fullRepository = [claims.owner, claims.project, repository].join(
|
|
1589
|
-
"/"
|
|
1590
|
-
);
|
|
1591
|
-
const imageRef = `${VCR_REGISTRY}/${fullRepository}:${tag}`;
|
|
2683
|
+
const target = await resolveRegistryTarget({
|
|
2684
|
+
repository,
|
|
2685
|
+
tag,
|
|
2686
|
+
span: buildSpan
|
|
2687
|
+
});
|
|
2688
|
+
const { token, claims, username, fullRepository, imageRef } = target;
|
|
1592
2689
|
buildSpan?.setAttributes({
|
|
1593
2690
|
"container.repository": fullRepository,
|
|
1594
2691
|
"image.tag": tag,
|
|
@@ -1625,31 +2722,7 @@ async function buildAndPushImage(params) {
|
|
|
1625
2722
|
buildArgs,
|
|
1626
2723
|
span: buildSpan
|
|
1627
2724
|
};
|
|
1628
|
-
|
|
1629
|
-
const authFile = forceLogin ? void 0 : existingRegistryAuthFile();
|
|
1630
|
-
if (authFile) {
|
|
1631
|
-
debug(`registry auth file present: ${authFile}`);
|
|
1632
|
-
step(`Using registry credentials from ${authFile}`);
|
|
1633
|
-
buildSpan?.setAttributes({
|
|
1634
|
-
"container.registry": VCR_REGISTRY,
|
|
1635
|
-
"registry.username": username,
|
|
1636
|
-
"registry.auth_file": authFile,
|
|
1637
|
-
"registry.login_skipped": toTag(true)
|
|
1638
|
-
});
|
|
1639
|
-
done("authenticated via provisioned credentials");
|
|
1640
|
-
} else {
|
|
1641
|
-
step(`Authenticating to ${VCR_REGISTRY} as ${username}`);
|
|
1642
|
-
await withSpan(
|
|
1643
|
-
buildSpan,
|
|
1644
|
-
"container.registry_login",
|
|
1645
|
-
{
|
|
1646
|
-
"container.registry": VCR_REGISTRY,
|
|
1647
|
-
"registry.username": username
|
|
1648
|
-
},
|
|
1649
|
-
() => engine.login(buildParams)
|
|
1650
|
-
);
|
|
1651
|
-
done("authenticated");
|
|
1652
|
-
}
|
|
2725
|
+
await authenticateRegistry(engine, buildParams, buildSpan);
|
|
1653
2726
|
await withSpan(
|
|
1654
2727
|
buildSpan,
|
|
1655
2728
|
"container.ensure_repository",
|
|
@@ -1703,47 +2776,70 @@ async function buildAndPushImage(params) {
|
|
|
1703
2776
|
}
|
|
1704
2777
|
);
|
|
1705
2778
|
}
|
|
1706
|
-
async function resolveImageHandler(options, span) {
|
|
1707
|
-
const { config,
|
|
1708
|
-
const entrypointRef = readString(entrypoint);
|
|
1709
|
-
const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
|
|
1710
|
-
const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
|
|
1711
|
-
const dockerfilePath = import_node_path6.default.join(workPath, dockerfileRel);
|
|
1712
|
-
const hasDockerfile = dockerfileConfigured !== void 0 || (0, import_node_fs6.existsSync)(dockerfilePath);
|
|
1713
|
-
const prebuiltImage = readString(config.handler) ?? (hasDockerfile ? void 0 : entrypointRef);
|
|
2779
|
+
async function resolveImageHandler(options, source, span) {
|
|
2780
|
+
const { config, meta } = options;
|
|
1714
2781
|
span?.setAttributes({
|
|
1715
|
-
"container.has_dockerfile": toTag(
|
|
2782
|
+
"container.has_dockerfile": toTag(source.kind === "dockerfile"),
|
|
1716
2783
|
"container.is_dev": toTag(Boolean(meta?.isDev))
|
|
1717
2784
|
});
|
|
1718
|
-
if (
|
|
1719
|
-
if (!prebuiltImage) {
|
|
1720
|
-
throw new Error(
|
|
1721
|
-
"Container service must specify an entrypoint: a prebuilt OCI image reference, or a Dockerfile path to build."
|
|
1722
|
-
);
|
|
1723
|
-
}
|
|
2785
|
+
if (source.kind === "prebuilt") {
|
|
1724
2786
|
span?.setAttributes({ "container.mode": "prebuilt" });
|
|
1725
|
-
info(`Using prebuilt image ${
|
|
1726
|
-
return
|
|
2787
|
+
info(`Using prebuilt image ${source.imageRef}`);
|
|
2788
|
+
return source.imageRef;
|
|
2789
|
+
}
|
|
2790
|
+
if (source.kind === "buildpack") {
|
|
2791
|
+
const { buildpack } = source;
|
|
2792
|
+
if (meta?.isDev) {
|
|
2793
|
+
const tag3 = devImageTag(options.service?.name ?? "service");
|
|
2794
|
+
span?.setAttributes({
|
|
2795
|
+
"container.mode": "buildpack-dev",
|
|
2796
|
+
"buildpack.runtime": buildpack.runtime,
|
|
2797
|
+
"image.tag": tag3
|
|
2798
|
+
});
|
|
2799
|
+
return tag3;
|
|
2800
|
+
}
|
|
2801
|
+
const repository2 = sanitizeRepository(
|
|
2802
|
+
options.service?.name ?? buildpack.runtime
|
|
2803
|
+
);
|
|
2804
|
+
const tag2 = resolveImageTag();
|
|
2805
|
+
span?.setAttributes({
|
|
2806
|
+
"container.mode": "buildpack-build-and-push",
|
|
2807
|
+
"buildpack.runtime": buildpack.runtime,
|
|
2808
|
+
"container.repository": repository2,
|
|
2809
|
+
"image.tag": tag2
|
|
2810
|
+
});
|
|
2811
|
+
return buildAndPushBuildpack({
|
|
2812
|
+
buildpack,
|
|
2813
|
+
files: options.files,
|
|
2814
|
+
workPath: options.workPath,
|
|
2815
|
+
repository: repository2,
|
|
2816
|
+
tag: tag2,
|
|
2817
|
+
config,
|
|
2818
|
+
meta,
|
|
2819
|
+
buildEnv: buildArgsFromEnv(meta?.buildEnv),
|
|
2820
|
+
parentSpan: span
|
|
2821
|
+
});
|
|
1727
2822
|
}
|
|
2823
|
+
const { dockerfileRel, dockerfilePath } = source;
|
|
1728
2824
|
if (meta?.isDev) {
|
|
1729
2825
|
const serviceName2 = options.service?.name;
|
|
1730
2826
|
const tag2 = devImageTag(
|
|
1731
|
-
serviceName2 ??
|
|
2827
|
+
serviceName2 ?? import_node_path14.default.basename(dockerfileRel).split(".")[0]
|
|
1732
2828
|
);
|
|
1733
2829
|
span?.setAttributes({ "container.mode": "dev", "image.tag": tag2 });
|
|
1734
2830
|
return tag2;
|
|
1735
2831
|
}
|
|
1736
|
-
if (!(0,
|
|
2832
|
+
if (!(0, import_node_fs14.existsSync)(dockerfilePath)) {
|
|
1737
2833
|
throw new Error(
|
|
1738
2834
|
`Dockerfile not found at "${dockerfilePath}" for container service.`
|
|
1739
2835
|
);
|
|
1740
2836
|
}
|
|
1741
2837
|
const serviceName = options.service?.name;
|
|
1742
2838
|
const repository = sanitizeRepository(
|
|
1743
|
-
serviceName ??
|
|
2839
|
+
serviceName ?? import_node_path14.default.basename(dockerfileRel).split(".")[0]
|
|
1744
2840
|
);
|
|
1745
2841
|
const tag = resolveImageTag();
|
|
1746
|
-
const contextDir =
|
|
2842
|
+
const contextDir = import_node_path14.default.dirname(dockerfilePath);
|
|
1747
2843
|
const buildArgs = buildArgsFromEnv(meta?.buildEnv);
|
|
1748
2844
|
span?.setAttributes({
|
|
1749
2845
|
"container.mode": "build_and_push",
|
|
@@ -1772,21 +2868,22 @@ function buildArgsFromEnv(env) {
|
|
|
1772
2868
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
1773
2869
|
}
|
|
1774
2870
|
async function build(options) {
|
|
2871
|
+
const source = resolveImageSource(options, "build");
|
|
1775
2872
|
const image = await withSpan(
|
|
1776
2873
|
options.span,
|
|
1777
2874
|
"container.resolve_image",
|
|
1778
2875
|
{ "service.name": options.service?.name },
|
|
1779
|
-
(span) => resolveImageHandler(options, span)
|
|
2876
|
+
(span) => resolveImageHandler(options, source, span)
|
|
1780
2877
|
);
|
|
1781
|
-
const lambdaOptions = await (0,
|
|
2878
|
+
const lambdaOptions = await (0, import_build_utils6.getLambdaOptionsFromFunction)({
|
|
1782
2879
|
sourceFile: resolveFunctionSourceFile(options),
|
|
1783
2880
|
config: options.config
|
|
1784
2881
|
});
|
|
1785
|
-
const command =
|
|
2882
|
+
const command = source.kind === "buildpack" ? void 0 : normalizeCommand(options.config.command);
|
|
1786
2883
|
await generateProjectManifest({
|
|
1787
2884
|
workPath: options.workPath,
|
|
1788
2885
|
framework: options.config.framework ?? void 0,
|
|
1789
|
-
serviceType: options.service ? (0,
|
|
2886
|
+
serviceType: options.service ? (0, import_build_utils6.getReportedServiceType)(options.service) : void 0
|
|
1790
2887
|
});
|
|
1791
2888
|
const routes = [
|
|
1792
2889
|
{ handle: "filesystem" },
|