@izykitten/ce 0.0.3-dev.3 → 0.0.3-dev.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -17
- package/dist/ce.js +898 -88
- package/package.json +1 -1
package/dist/ce.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
// src/command.ts
|
|
2
2
|
var VALUE_OPTIONS = {
|
|
3
3
|
docker: {
|
|
4
|
-
long: new Set([
|
|
4
|
+
long: new Set([
|
|
5
|
+
"config",
|
|
6
|
+
"context",
|
|
7
|
+
"host",
|
|
8
|
+
"log-level",
|
|
9
|
+
"tlscacert",
|
|
10
|
+
"tlscert",
|
|
11
|
+
"tlskey",
|
|
12
|
+
"builder"
|
|
13
|
+
]),
|
|
5
14
|
short: "cHl"
|
|
6
15
|
},
|
|
7
16
|
podman: {
|
|
@@ -86,7 +95,11 @@ var COMMANDS = {
|
|
|
86
95
|
{ words: ["buildx", "b"] },
|
|
87
96
|
{ words: ["buildx", "imagetools", "create"], tagsOnly: true }
|
|
88
97
|
],
|
|
89
|
-
podman: [
|
|
98
|
+
podman: [
|
|
99
|
+
{ words: ["build"] },
|
|
100
|
+
{ words: ["image", "build"] },
|
|
101
|
+
{ words: ["buildx", "build"] }
|
|
102
|
+
],
|
|
90
103
|
buildah: [{ words: ["build"] }, { words: ["bud"] }]
|
|
91
104
|
};
|
|
92
105
|
function findBuildCommand(engine, argv) {
|
|
@@ -111,6 +124,10 @@ function findBuildCommand(engine, argv) {
|
|
|
111
124
|
return null;
|
|
112
125
|
}
|
|
113
126
|
|
|
127
|
+
// src/containerfile.ts
|
|
128
|
+
import fs from "node:fs";
|
|
129
|
+
import path from "node:path";
|
|
130
|
+
|
|
114
131
|
// src/errors.ts
|
|
115
132
|
class CeError extends Error {
|
|
116
133
|
name = "CeError";
|
|
@@ -125,16 +142,738 @@ function errorMessage(exc) {
|
|
|
125
142
|
return exc instanceof Error ? exc.message : String(exc);
|
|
126
143
|
}
|
|
127
144
|
|
|
145
|
+
// src/containerfile.ts
|
|
146
|
+
class Unreadable extends Error {
|
|
147
|
+
located = false;
|
|
148
|
+
}
|
|
149
|
+
var fail = (message) => {
|
|
150
|
+
throw new Unreadable(message);
|
|
151
|
+
};
|
|
152
|
+
var STANDARD_SYNTAX = /^(?:docker\.io\/)?docker\/dockerfile(?:-upstream)?(?::(?:1|latest|labs|master|1-labs|master-labs|1\.(?:2[1-9]|[3-9]\d|\d{3,})(?:\.\d+)?(?:-[\w.]+)?(?:@sha256:[0-9a-f]{64})?))?$/u;
|
|
153
|
+
var isRemote = (value) => value === "-" || value.includes("://") || /^(?:git@|github\.com\/)/u.test(value);
|
|
154
|
+
var familiar = (name) => name.replace(/^(?:(?:index\.)?docker\.io\/)?(?:library\/(?=[^/]+$))?/u, "").replace(/:latest$/u, "");
|
|
155
|
+
function findContainerfile(files, cwd = process.cwd()) {
|
|
156
|
+
try {
|
|
157
|
+
const file = locate(files, cwd);
|
|
158
|
+
if (files.engine !== "docker" && file.endsWith(".in"))
|
|
159
|
+
fail(`${files.engine} runs ${JSON.stringify(file)} through cpp, which ce does not`);
|
|
160
|
+
return file;
|
|
161
|
+
} catch (exc) {
|
|
162
|
+
if (exc instanceof Unreadable)
|
|
163
|
+
throw exc;
|
|
164
|
+
return fail(`ce cannot look for the Containerfile: ${errorMessage(exc)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function locate({ engine, files, positionals }, cwd) {
|
|
168
|
+
const docker = engine === "docker";
|
|
169
|
+
if (positionals.length > 1)
|
|
170
|
+
fail(`ce cannot tell which of ${positionals.map((p) => JSON.stringify(p)).join(", ")} is the build context`);
|
|
171
|
+
const context = positionals[0] ?? (docker ? fail("the command has no build context") : ".");
|
|
172
|
+
if (isRemote(context))
|
|
173
|
+
fail(`the build context ${JSON.stringify(context)} is not a local directory`);
|
|
174
|
+
const contextDir = path.resolve(cwd, context);
|
|
175
|
+
if (!docker && (files.length > 1 || files.some((f) => f.includes(","))))
|
|
176
|
+
fail(`ce does not read ${engine}'s several -f files`);
|
|
177
|
+
const file = files.at(-1) || undefined;
|
|
178
|
+
if (file === undefined && files.length && !docker)
|
|
179
|
+
fail("the -f is empty");
|
|
180
|
+
if (docker) {
|
|
181
|
+
if (file !== undefined && isRemote(file))
|
|
182
|
+
fail(`-f ${JSON.stringify(file)} is not a local file`);
|
|
183
|
+
return lowercaseDockerfile(file === undefined ? path.join(contextDir, "Dockerfile") : path.resolve(cwd, file));
|
|
184
|
+
}
|
|
185
|
+
if (file !== undefined) {
|
|
186
|
+
if (isRemote(file))
|
|
187
|
+
fail(`-f ${JSON.stringify(file)} is not a local file`);
|
|
188
|
+
const given = path.resolve(cwd, file);
|
|
189
|
+
return fs.existsSync(given) ? given : path.resolve(contextDir, file);
|
|
190
|
+
}
|
|
191
|
+
if (fs.statSync(contextDir, { throwIfNoEntry: false })?.isFile())
|
|
192
|
+
return contextDir;
|
|
193
|
+
const containerfile = path.join(contextDir, "Containerfile");
|
|
194
|
+
return fs.existsSync(containerfile) ? containerfile : path.join(contextDir, "Dockerfile");
|
|
195
|
+
}
|
|
196
|
+
function lowercaseDockerfile(file) {
|
|
197
|
+
if (path.basename(file) !== "Dockerfile")
|
|
198
|
+
return file;
|
|
199
|
+
let names;
|
|
200
|
+
try {
|
|
201
|
+
names = fs.readdirSync(path.dirname(file));
|
|
202
|
+
} catch {
|
|
203
|
+
return file;
|
|
204
|
+
}
|
|
205
|
+
return !names.includes("Dockerfile") && names.includes("dockerfile") ? path.join(path.dirname(file), "dockerfile") : file;
|
|
206
|
+
}
|
|
207
|
+
var SPACE = "\\t\\n\\v\\f\\r \\u0085\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000";
|
|
208
|
+
var IS_SPACE = new RegExp(`[${SPACE}]`, "u");
|
|
209
|
+
var TRIM_START = new RegExp(`^[${SPACE}]+`, "u");
|
|
210
|
+
var UNUSUAL_SPACE = /[\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/u;
|
|
211
|
+
var NON_ASCII = /[^\0-\x7f]/u;
|
|
212
|
+
function instructionOf(word, line) {
|
|
213
|
+
if (NON_ASCII.test(word))
|
|
214
|
+
fail(`ce cannot read the instruction ${JSON.stringify(word)} on line ${line}`);
|
|
215
|
+
return word.toUpperCase();
|
|
216
|
+
}
|
|
217
|
+
var stageKey = (name) => NON_ASCII.test(name) ? fail(`ce reads only ASCII stage names, not ${JSON.stringify(name)}`) : name.toLowerCase();
|
|
218
|
+
function trimEnd(text, space) {
|
|
219
|
+
let end = text.length;
|
|
220
|
+
while (end > 0 && space.test(text[end - 1]))
|
|
221
|
+
end--;
|
|
222
|
+
return text.slice(0, end);
|
|
223
|
+
}
|
|
224
|
+
var trim = (text) => trimEnd(text.replace(TRIM_START, ""), IS_SPACE);
|
|
225
|
+
var COMMAND = /^([^\t\v\f\r ]+)[\t\v\f\r ]*([\s\S]*)$/u;
|
|
226
|
+
function words(text, escape, heredoc = false) {
|
|
227
|
+
const out = [];
|
|
228
|
+
let word = "";
|
|
229
|
+
let quote = "";
|
|
230
|
+
let inWord = false;
|
|
231
|
+
for (let i = 0;i < text.length; i++) {
|
|
232
|
+
const ch = text[i];
|
|
233
|
+
if (!quote && IS_SPACE.test(ch)) {
|
|
234
|
+
if (inWord)
|
|
235
|
+
out.push(word);
|
|
236
|
+
[word, inWord] = ["", false];
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
inWord = true;
|
|
240
|
+
if (ch === escape && quote !== "'" && i + 1 === text.length)
|
|
241
|
+
continue;
|
|
242
|
+
word += ch;
|
|
243
|
+
if (heredoc && !quote && ch === "<" && text[i + 1] === "<") {
|
|
244
|
+
word += text[++i];
|
|
245
|
+
while (/[ \t\r]/u.test(text[i + 1] ?? ""))
|
|
246
|
+
word += text[++i];
|
|
247
|
+
} else if (ch === escape && quote !== "'" && i + 1 < text.length)
|
|
248
|
+
word += text[++i];
|
|
249
|
+
else if (ch === quote)
|
|
250
|
+
quote = "";
|
|
251
|
+
else if (!quote && (ch === '"' || ch === "'"))
|
|
252
|
+
quote = ch;
|
|
253
|
+
}
|
|
254
|
+
if (quote)
|
|
255
|
+
fail(`unterminated ${quote} in ${JSON.stringify(text)}`);
|
|
256
|
+
if (inWord)
|
|
257
|
+
out.push(word);
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
var HEREDOC_INSTRUCTIONS = ["RUN", "COPY", "ADD"];
|
|
261
|
+
function heredocs(keyword, rest, line, buildkit) {
|
|
262
|
+
while (keyword === "ONBUILD") {
|
|
263
|
+
const [, inner = "", innerRest = ""] = COMMAND.exec(trim(rest)) ?? [];
|
|
264
|
+
[keyword, rest] = [instructionOf(inner, line), innerRest];
|
|
265
|
+
}
|
|
266
|
+
if (!HEREDOC_INSTRUCTIONS.includes(keyword) || !rest.includes("<<"))
|
|
267
|
+
return [];
|
|
268
|
+
const body = rest.replace(/^(?:--\S+\s+)*/u, "").replace(TRIM_START, "");
|
|
269
|
+
if (body.startsWith("[")) {
|
|
270
|
+
const json = buildkit ? body : jsonPrefix(body);
|
|
271
|
+
try {
|
|
272
|
+
const array = json === undefined ? undefined : JSON.parse(json);
|
|
273
|
+
if (Array.isArray(array) && array.every((v) => typeof v === "string"))
|
|
274
|
+
return [];
|
|
275
|
+
} catch {}
|
|
276
|
+
}
|
|
277
|
+
const unsure = () => fail(`ce cannot tell which heredocs the << on line ${line} starts`);
|
|
278
|
+
if (UNUSUAL_SPACE.test(rest) || rest.replace(PLAIN_BRACES, "").includes("${"))
|
|
279
|
+
unsure();
|
|
280
|
+
const found = [];
|
|
281
|
+
let split;
|
|
282
|
+
try {
|
|
283
|
+
split = words(rest, "\\", true);
|
|
284
|
+
} catch {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
for (const word of split) {
|
|
288
|
+
const match = /^\d*<<(-?)[\t\n\f\r ]*([^<]*)$/u.exec(word);
|
|
289
|
+
if (!match?.[2])
|
|
290
|
+
continue;
|
|
291
|
+
const delimiter = /^(["']?)([^\s"'\\`$]+)\1$/u.exec(match[2]);
|
|
292
|
+
found.push({ name: delimiter?.[2] ?? unsure(), tabs: match[1] === "-" });
|
|
293
|
+
}
|
|
294
|
+
return found;
|
|
295
|
+
}
|
|
296
|
+
var PLAIN_BRACES = /\$\{[A-Za-z_][A-Za-z0-9_]*(?:(?::[-+?]|[-+?#%])[^\s"'\\`<${}]*)?\}/gu;
|
|
297
|
+
function jsonPrefix(text) {
|
|
298
|
+
let depth = 0;
|
|
299
|
+
for (let i = 0;i < text.length; i++) {
|
|
300
|
+
const ch = text[i];
|
|
301
|
+
if (ch === '"') {
|
|
302
|
+
for (i++;i < text.length && text[i] !== '"'; i++)
|
|
303
|
+
if (text[i] === "\\")
|
|
304
|
+
i++;
|
|
305
|
+
} else if (ch === "[" || ch === "{")
|
|
306
|
+
depth++;
|
|
307
|
+
else if ((ch === "]" || ch === "}") && --depth === 0)
|
|
308
|
+
return text.slice(0, i + 1);
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
var DIRECTIVE_SPACE = /[\t\n\f\r ]/u;
|
|
313
|
+
function directive(text) {
|
|
314
|
+
const match = /^([a-zA-Z][a-zA-Z0-9]*)[\t\n\f\r ]*=[\t\n\f\r ]*([^\n]+)$/u.exec(trimEnd(text, DIRECTIVE_SPACE));
|
|
315
|
+
return match ? { key: match[1].toLowerCase(), value: match[2] } : undefined;
|
|
316
|
+
}
|
|
317
|
+
var DIRECTIVES = ["syntax", "escape", "check"];
|
|
318
|
+
function detectSyntax(lines) {
|
|
319
|
+
if (lines[0]?.startsWith("#!"))
|
|
320
|
+
lines = lines.slice(1);
|
|
321
|
+
for (const prefix of ["#", "//"]) {
|
|
322
|
+
const seen = new Set;
|
|
323
|
+
for (const line of lines) {
|
|
324
|
+
if (!line.startsWith(prefix))
|
|
325
|
+
break;
|
|
326
|
+
const match = directive(line.slice(prefix.length).replace(TRIM_START, ""));
|
|
327
|
+
if (!match || !DIRECTIVES.includes(match.key) || seen.has(match.key))
|
|
328
|
+
break;
|
|
329
|
+
seen.add(match.key);
|
|
330
|
+
if (match.key === "syntax")
|
|
331
|
+
return match.value.split(" ")[0];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
function pair(word) {
|
|
337
|
+
const eq = word.indexOf("=");
|
|
338
|
+
return eq < 0 ? { name: word, value: undefined } : { name: word.slice(0, eq), value: word.slice(eq + 1) };
|
|
339
|
+
}
|
|
340
|
+
function parseContainerfile(text, model = "buildkit") {
|
|
341
|
+
const buildkit = model === "buildkit";
|
|
342
|
+
const lines = text.replace(/^\ufeff/u, "").split(`
|
|
343
|
+
`).map((line, at) => line.endsWith("\r\r") ? fail(`ce reads only LF and CRLF line ends, not line ${at + 1}'s`) : line.replace(/\r$/u, ""));
|
|
344
|
+
let escape = "\\";
|
|
345
|
+
let i = 0;
|
|
346
|
+
for (let seen = false;!buildkit && i < lines.length; i++) {
|
|
347
|
+
const directive = /^#[ \t]*escape[ \t]*=[ \t]*([^\n])/u.exec(lines[i].replace(TRIM_START, "").toLowerCase());
|
|
348
|
+
if (!directive)
|
|
349
|
+
break;
|
|
350
|
+
if (seen)
|
|
351
|
+
fail("there is more than one escape directive");
|
|
352
|
+
seen = true;
|
|
353
|
+
escape = directive[1];
|
|
354
|
+
if (escape !== "\\" && escape !== "`")
|
|
355
|
+
fail(`the escape directive ${JSON.stringify(escape)} is not \\ or \``);
|
|
356
|
+
}
|
|
357
|
+
for (const seen = new Set;buildkit && i < lines.length; i++) {
|
|
358
|
+
const line = lines[i].replace(TRIM_START, "");
|
|
359
|
+
const found = line.startsWith("#") ? directive(line.slice(1).replace(TRIM_START, "")) : undefined;
|
|
360
|
+
if (!found || !DIRECTIVES.includes(found.key) || seen.has(found.key))
|
|
361
|
+
break;
|
|
362
|
+
seen.add(found.key);
|
|
363
|
+
if (found.key === "escape") {
|
|
364
|
+
const { value } = found;
|
|
365
|
+
if (value !== "\\" && value !== "`")
|
|
366
|
+
fail(`the escape directive ${JSON.stringify(value)} is not \\ or \``);
|
|
367
|
+
escape = value;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const e = escape === "\\" ? "\\\\" : escape;
|
|
371
|
+
const continued = buildkit ? new RegExp(`([^${e}])${e}[ \\t]*$|^${e}[ \\t]*$`, "u") : new RegExp(`()${e}[ \\t]*$`, "u");
|
|
372
|
+
const parsed = {
|
|
373
|
+
escape,
|
|
374
|
+
syntax: detectSyntax(lines),
|
|
375
|
+
globals: [],
|
|
376
|
+
stages: []
|
|
377
|
+
};
|
|
378
|
+
while (i < lines.length) {
|
|
379
|
+
let start = 0;
|
|
380
|
+
let line = "";
|
|
381
|
+
for (;i < lines.length; i++) {
|
|
382
|
+
const physical = lines[i];
|
|
383
|
+
if (/^(?:#|$)/u.test(trim(physical)))
|
|
384
|
+
continue;
|
|
385
|
+
start ||= i + 1;
|
|
386
|
+
if (continued.test(physical)) {
|
|
387
|
+
line += physical.replace(continued, "$1");
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
line += physical;
|
|
391
|
+
i++;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
line = trim(line);
|
|
395
|
+
if (!line)
|
|
396
|
+
continue;
|
|
397
|
+
const [instruction = "", rest = ""] = COMMAND.exec(line).slice(1);
|
|
398
|
+
const keyword = instructionOf(instruction, start);
|
|
399
|
+
for (const { name, tabs } of heredocs(keyword, rest, start, buildkit)) {
|
|
400
|
+
for (;; i++) {
|
|
401
|
+
if (i >= lines.length)
|
|
402
|
+
fail(`the heredoc ${name} from line ${start} never ends`);
|
|
403
|
+
const end = lines[i];
|
|
404
|
+
if ((tabs ? end.replace(/^\t+/u, "") : end) === name) {
|
|
405
|
+
i++;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const [, onbuild = "", onbuildRest = ""] = keyword === "ONBUILD" ? COMMAND.exec(trim(rest)) ?? [] : [];
|
|
411
|
+
const kind = onbuild ? instructionOf(onbuild, start) : keyword;
|
|
412
|
+
const args = onbuild ? onbuildRest : rest;
|
|
413
|
+
if (keyword !== "FROM" && kind !== "ARG" && kind !== "ENV")
|
|
414
|
+
continue;
|
|
415
|
+
if (UNUSUAL_SPACE.test(line))
|
|
416
|
+
fail(`ce reads only ASCII space in the ${kind} on line ${start}`);
|
|
417
|
+
const split = onbuild && buildkit ? "\\" : escape;
|
|
418
|
+
const stage = parsed.stages.at(-1);
|
|
419
|
+
if (keyword === "FROM") {
|
|
420
|
+
const all = words(rest, buildkit ? escape : "\\");
|
|
421
|
+
let flags = 0;
|
|
422
|
+
while (all[flags]?.startsWith("--"))
|
|
423
|
+
if (all[flags++] === "--")
|
|
424
|
+
break;
|
|
425
|
+
const parts = all.slice(flags);
|
|
426
|
+
if (!(parts.length === 1 || parts.length === 3 && /^[Aa][Ss]$/u.test(parts[1])) || parts.some((part) => IS_SPACE.test(part)))
|
|
427
|
+
fail(`ce cannot read the FROM on line ${start}`);
|
|
428
|
+
parsed.stages.push({
|
|
429
|
+
name: parts[2] ?? "",
|
|
430
|
+
line: start,
|
|
431
|
+
base: parts[0],
|
|
432
|
+
instructions: []
|
|
433
|
+
});
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
const set = words(args, split);
|
|
437
|
+
if (set[0]?.startsWith("--"))
|
|
438
|
+
fail(`ce does not read the flags of the ${kind} on line ${start}`);
|
|
439
|
+
const from = {
|
|
440
|
+
line: start,
|
|
441
|
+
...onbuild !== "" && { onbuild: true }
|
|
442
|
+
};
|
|
443
|
+
if (kind === "ARG" && (stage || !onbuild)) {
|
|
444
|
+
if (!set.length)
|
|
445
|
+
fail(`the ARG on line ${start} names nothing`);
|
|
446
|
+
(stage?.instructions ?? parsed.globals).push({
|
|
447
|
+
kind: "ARG",
|
|
448
|
+
pairs: set.map(pair),
|
|
449
|
+
...from
|
|
450
|
+
});
|
|
451
|
+
} else if (kind === "ENV" && stage) {
|
|
452
|
+
const legacy = set.length > 0 && !set[0].includes("=");
|
|
453
|
+
stage.instructions.push({
|
|
454
|
+
kind: "ENV",
|
|
455
|
+
pairs: legacy ? [{ name: set[0], value: undefined }] : set.map(pair),
|
|
456
|
+
...from
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return parsed;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
class Unknown {
|
|
464
|
+
why;
|
|
465
|
+
reason;
|
|
466
|
+
constructor(why, reason = "") {
|
|
467
|
+
this.why = why;
|
|
468
|
+
this.reason = reason;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
var BASE_ENV = new Unknown("base");
|
|
472
|
+
var BUILTINS = [
|
|
473
|
+
"BUILDPLATFORM",
|
|
474
|
+
"BUILDOS",
|
|
475
|
+
"BUILDOSVERSION",
|
|
476
|
+
"BUILDARCH",
|
|
477
|
+
"BUILDVARIANT",
|
|
478
|
+
"TARGETPLATFORM",
|
|
479
|
+
"TARGETOS",
|
|
480
|
+
"TARGETOSVERSION",
|
|
481
|
+
"TARGETARCH",
|
|
482
|
+
"TARGETVARIANT",
|
|
483
|
+
"TARGETSTAGE"
|
|
484
|
+
];
|
|
485
|
+
var INJECTED = /^(?:SOURCE_DATE_EPOCH|(?:HTTPS?|FTP|NO|ALL)_PROXY)$/iu;
|
|
486
|
+
var PROXIES = /^(?:(?:HTTPS?|FTP|NO)_PROXY|(?:https?|ftp|no)_proxy)$/u;
|
|
487
|
+
var NON_ENV = ["BUILDKIT_SBOM_SCAN_CONTEXT", "BUILDKIT_SBOM_SCAN_STAGE"];
|
|
488
|
+
function expandWord(word, escape, model, lookup) {
|
|
489
|
+
const buildkit = model === "buildkit";
|
|
490
|
+
let i = 0;
|
|
491
|
+
let depth = 0;
|
|
492
|
+
const value = (name) => {
|
|
493
|
+
const v = lookup(name);
|
|
494
|
+
if (v instanceof Unknown) {
|
|
495
|
+
if (v.why !== "error")
|
|
496
|
+
fail(`ce cannot tell what $${name} is there`);
|
|
497
|
+
const exc = new Unreadable(v.reason);
|
|
498
|
+
exc.located = true;
|
|
499
|
+
throw exc;
|
|
500
|
+
}
|
|
501
|
+
return v;
|
|
502
|
+
};
|
|
503
|
+
const readName = () => {
|
|
504
|
+
const rest = word.slice(i);
|
|
505
|
+
const name = (buildkit ? /^(?:[0-9]+|[@*#?\-$!]|[A-Za-z_][A-Za-z0-9_]*)/u.exec(rest)?.[0] : /^(?:[0-9]|[A-Za-z0-9_]+)/u.exec(rest)?.[0]) ?? "";
|
|
506
|
+
const after = rest.slice(name.length);
|
|
507
|
+
if (NON_ASCII.test(after[0] ?? "") && /^[\p{L}\p{Nd}]/u.test(after))
|
|
508
|
+
fail(`ce reads only ASCII names after a $, not in ${JSON.stringify(word)}`);
|
|
509
|
+
i += name.length;
|
|
510
|
+
return name;
|
|
511
|
+
};
|
|
512
|
+
const unsupported = () => fail(`ce does not evaluate the \${} in ${JSON.stringify(word)}`);
|
|
513
|
+
const dollar = () => {
|
|
514
|
+
i++;
|
|
515
|
+
if (word[i] !== "{") {
|
|
516
|
+
const name = readName();
|
|
517
|
+
return name ? value(name) ?? "" : "$";
|
|
518
|
+
}
|
|
519
|
+
i++;
|
|
520
|
+
const name = readName();
|
|
521
|
+
if (!name)
|
|
522
|
+
unsupported();
|
|
523
|
+
if (word[i] === "}") {
|
|
524
|
+
i++;
|
|
525
|
+
return value(name) ?? "";
|
|
526
|
+
}
|
|
527
|
+
const op = (buildkit ? /^:?[-+]/u : /^:[-+]/u).exec(word.slice(i))?.[0];
|
|
528
|
+
if (!op)
|
|
529
|
+
unsupported();
|
|
530
|
+
i += op.length;
|
|
531
|
+
if (++depth > 100)
|
|
532
|
+
fail("ce does not evaluate ${} nested this deeply");
|
|
533
|
+
const alternative = until("}");
|
|
534
|
+
depth--;
|
|
535
|
+
const v = value(name);
|
|
536
|
+
const set = op.startsWith(":") ? v !== undefined && v !== "" : v !== undefined;
|
|
537
|
+
return op.endsWith("-") ? set ? v : alternative : set ? alternative : "";
|
|
538
|
+
};
|
|
539
|
+
const until = (end) => {
|
|
540
|
+
let out = "";
|
|
541
|
+
while (i < word.length) {
|
|
542
|
+
const ch = word[i];
|
|
543
|
+
if (end && ch === end) {
|
|
544
|
+
i++;
|
|
545
|
+
return out;
|
|
546
|
+
}
|
|
547
|
+
if (ch === escape) {
|
|
548
|
+
out += word[i + 1] ?? "";
|
|
549
|
+
i += 2;
|
|
550
|
+
} else if (ch === "'") {
|
|
551
|
+
const close = word.indexOf("'", i + 1);
|
|
552
|
+
if (close < 0)
|
|
553
|
+
fail(`unterminated ' in ${JSON.stringify(word)}`);
|
|
554
|
+
out += word.slice(i + 1, close);
|
|
555
|
+
i = close + 1;
|
|
556
|
+
} else if (ch === '"') {
|
|
557
|
+
i++;
|
|
558
|
+
for (;; ) {
|
|
559
|
+
if (i >= word.length)
|
|
560
|
+
fail(`unterminated " in ${JSON.stringify(word)}`);
|
|
561
|
+
const c = word[i];
|
|
562
|
+
if (c === '"') {
|
|
563
|
+
i++;
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
if (c === escape && ['"', "$", escape].includes(word[i + 1] ?? "")) {
|
|
567
|
+
out += word[i + 1];
|
|
568
|
+
i += 2;
|
|
569
|
+
} else if (c === "$")
|
|
570
|
+
out += dollar();
|
|
571
|
+
else {
|
|
572
|
+
out += c;
|
|
573
|
+
i++;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
} else if (ch === "$")
|
|
577
|
+
out += dollar();
|
|
578
|
+
else {
|
|
579
|
+
out += ch;
|
|
580
|
+
i++;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (end)
|
|
584
|
+
fail(`missing ${end} in ${JSON.stringify(word)}`);
|
|
585
|
+
return out;
|
|
586
|
+
};
|
|
587
|
+
return until("");
|
|
588
|
+
}
|
|
589
|
+
function argDefault(parsed, name, target, buildArg, model = "buildkit", { contexts = [], envs = [] } = {}) {
|
|
590
|
+
const buildkit = model === "buildkit";
|
|
591
|
+
const escape = buildkit ? parsed.escape : "\\";
|
|
592
|
+
const evaluate = (word, line, lookup) => {
|
|
593
|
+
try {
|
|
594
|
+
return expandWord(word, escape, model, lookup);
|
|
595
|
+
} catch (exc) {
|
|
596
|
+
if (!(exc instanceof Unreadable))
|
|
597
|
+
throw exc;
|
|
598
|
+
return new Unknown("error", exc.located ? exc.message : `${exc.message}, on line ${line}`);
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
const isBuiltin = (arg) => BUILTINS.includes(arg);
|
|
602
|
+
const plain = (name) => ![...`$'"`, escape].some((ch) => name.includes(ch));
|
|
603
|
+
const named = (kind, name, line) => plain(name) ? name : fail(`ce cannot tell what name the ${kind} on line ${line} sets`);
|
|
604
|
+
const globals = new Map;
|
|
605
|
+
for (const builtin of BUILTINS)
|
|
606
|
+
globals.set(builtin, buildArg(builtin) ?? new Unknown("builtin"));
|
|
607
|
+
for (const { pairs, line } of parsed.globals) {
|
|
608
|
+
const before = new Map(globals);
|
|
609
|
+
const seen = (ref) => {
|
|
610
|
+
const known = buildkit ? globals : before;
|
|
611
|
+
if (known.has(ref) || buildkit || !PROXIES.test(ref))
|
|
612
|
+
return known.get(ref);
|
|
613
|
+
return buildArg(ref) ?? new Unknown("injected");
|
|
614
|
+
};
|
|
615
|
+
for (const { name, value } of pairs) {
|
|
616
|
+
const arg = named("ARG", name, line);
|
|
617
|
+
const given = buildArg(arg);
|
|
618
|
+
if (given !== undefined)
|
|
619
|
+
globals.set(arg, given);
|
|
620
|
+
else if (INJECTED.test(arg))
|
|
621
|
+
globals.set(arg, new Unknown("injected"));
|
|
622
|
+
else if (value !== undefined && !(isBuiltin(arg) && !buildkit))
|
|
623
|
+
globals.set(arg, evaluate(value, line, seen));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const findStage = (wanted, before, target = false) => {
|
|
627
|
+
const at = parsed.stages.findLastIndex((s, i) => i < before && (buildkit ? stageKey(s.name) === stageKey(wanted) : (s.name || String(i)) === wanted || !target && String(i) === wanted));
|
|
628
|
+
if (at >= 0 || buildkit || !target || !/^\d+$/u.test(wanted))
|
|
629
|
+
return at;
|
|
630
|
+
const position = Number(wanted);
|
|
631
|
+
return position < before ? position : -1;
|
|
632
|
+
};
|
|
633
|
+
const replaced = (index) => {
|
|
634
|
+
const stage = parsed.stages[index].name;
|
|
635
|
+
return buildkit && stage ? contexts.find((c) => stageKey(familiar(c)) === stageKey(stage)) : undefined;
|
|
636
|
+
};
|
|
637
|
+
for (const stage of parsed.stages) {
|
|
638
|
+
if (!buildkit && !plain(stage.name))
|
|
639
|
+
fail(`ce cannot tell what the stage ${JSON.stringify(stage.name)} is named`);
|
|
640
|
+
}
|
|
641
|
+
const source = (index) => {
|
|
642
|
+
const stage = parsed.stages[index];
|
|
643
|
+
const base = evaluate(stage.base, stage.line, (ref) => globals.get(ref));
|
|
644
|
+
let parent = typeof base === "string" ? findStage(base, index) : -1;
|
|
645
|
+
const swapped = buildkit ? parent >= 0 ? replaced(parent) : undefined : contexts.find((c) => c === stage.base || c === base);
|
|
646
|
+
if (swapped !== undefined)
|
|
647
|
+
parent = -1;
|
|
648
|
+
return { base, parent, swapped };
|
|
649
|
+
};
|
|
650
|
+
const states = new Map;
|
|
651
|
+
const run = (index) => {
|
|
652
|
+
const known = states.get(index);
|
|
653
|
+
if (known)
|
|
654
|
+
return known;
|
|
655
|
+
const stage = parsed.stages[index];
|
|
656
|
+
const { base, parent, swapped } = source(index);
|
|
657
|
+
const from = parent >= 0 ? run(parent) : undefined;
|
|
658
|
+
const state = {
|
|
659
|
+
env: new Map,
|
|
660
|
+
args: new Map,
|
|
661
|
+
image: new Map(from?.image),
|
|
662
|
+
opaque: from?.opaque ?? (swapped !== undefined || base !== "scratch")
|
|
663
|
+
};
|
|
664
|
+
const lost = (name) => new Unknown("error", `--build-context ${name} replaces what the FROM on line ${stage.line} builds from, so ce cannot tell what it sets`);
|
|
665
|
+
let triggers = [];
|
|
666
|
+
if (buildkit) {
|
|
667
|
+
if (base instanceof Unknown)
|
|
668
|
+
state.undeclared = new Unknown("error", `ce cannot tell what the FROM on line ${stage.line} is built from`);
|
|
669
|
+
else if (swapped !== undefined)
|
|
670
|
+
state.undeclared = lost(swapped);
|
|
671
|
+
else if (from) {
|
|
672
|
+
state.args = new Map(from.args);
|
|
673
|
+
state.undeclared = from.undeclared;
|
|
674
|
+
for (const [k, v] of from.env)
|
|
675
|
+
state.env.set(k, from.opaque || k === "PATH" ? BASE_ENV : v);
|
|
676
|
+
for (const [k, v] of from.image)
|
|
677
|
+
state.env.set(k, v);
|
|
678
|
+
triggers = parsed.stages[parent].instructions.filter((i) => i.onbuild);
|
|
679
|
+
}
|
|
680
|
+
} else {
|
|
681
|
+
if (from)
|
|
682
|
+
state.env = new Map(from.env);
|
|
683
|
+
for (const flag of envs) {
|
|
684
|
+
const spec = flag.slice(flag.indexOf(" ") + 1);
|
|
685
|
+
const names = flag.startsWith("--unsetenv ") ? spec.includes('"') ? ["*"] : spec.split(",") : [spec.split("=")[0].replace(/-$/u, "")];
|
|
686
|
+
const set = (k) => state.env.set(k, new Unknown("error", `${k} may be changed by ${flag}`));
|
|
687
|
+
for (const env of names)
|
|
688
|
+
if (env.includes("*"))
|
|
689
|
+
[...state.env.keys()].forEach(set);
|
|
690
|
+
else
|
|
691
|
+
set(env);
|
|
692
|
+
}
|
|
693
|
+
const written = findStage(stage.base, index);
|
|
694
|
+
if (swapped !== undefined)
|
|
695
|
+
state.undeclared = lost(swapped);
|
|
696
|
+
else if (written >= 0)
|
|
697
|
+
state.args = inheritedArgs(written);
|
|
698
|
+
for (const { kind, pairs, line } of parent >= 0 ? parsed.stages[parent].instructions.filter((i) => i.onbuild) : []) {
|
|
699
|
+
for (const { name: arg } of pairs) {
|
|
700
|
+
if (!plain(arg))
|
|
701
|
+
fail(`ce cannot tell what name the ONBUILD on line ${line} sets`);
|
|
702
|
+
(kind === "ENV" ? state.env : state.args).set(arg, new Unknown("error", `${arg} may be set by the ONBUILD on line ${line}, which ${model} runs only for some image formats`));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const see = (env) => (ref) => env.has(ref) ? env.get(ref) : BASE_ENV;
|
|
707
|
+
const own = stage.instructions.filter((i) => !i.onbuild);
|
|
708
|
+
for (const { kind, pairs, line } of [...triggers, ...own]) {
|
|
709
|
+
const before = new Map(state.env);
|
|
710
|
+
for (const { name, value } of pairs) {
|
|
711
|
+
const arg = named(kind, name, line);
|
|
712
|
+
const seen = buildkit && kind === "ARG" ? state.env : before;
|
|
713
|
+
const evaluated = (word) => evaluate(word, line, see(seen));
|
|
714
|
+
if (kind === "ENV") {
|
|
715
|
+
const v = value === undefined ? new Unknown("error", `ce does not read the ENV ${arg} on line ${line}`) : evaluated(value);
|
|
716
|
+
state.env.set(arg, v);
|
|
717
|
+
state.image.set(arg, v);
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
const given = buildArg(arg);
|
|
721
|
+
let v;
|
|
722
|
+
if (given !== undefined)
|
|
723
|
+
v = given;
|
|
724
|
+
else if (INJECTED.test(arg))
|
|
725
|
+
v = new Unknown("injected");
|
|
726
|
+
else if (isBuiltin(arg) && (!buildkit || value === undefined))
|
|
727
|
+
v = globals.get(arg);
|
|
728
|
+
else if (value !== undefined)
|
|
729
|
+
v = evaluated(value);
|
|
730
|
+
else if (globals.get(arg) !== undefined)
|
|
731
|
+
v = globals.get(arg);
|
|
732
|
+
else {
|
|
733
|
+
if (!state.args.has(arg))
|
|
734
|
+
state.args.set(arg, state.undeclared);
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
state.args.set(arg, v);
|
|
738
|
+
if (buildkit && !NON_ENV.includes(arg))
|
|
739
|
+
state.env.set(arg, v);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
states.set(index, state);
|
|
743
|
+
return state;
|
|
744
|
+
};
|
|
745
|
+
const inherited = new Map;
|
|
746
|
+
const inheritedArgs = (index) => {
|
|
747
|
+
const todo = [];
|
|
748
|
+
let i = index;
|
|
749
|
+
while (i >= 0 && !inherited.has(i)) {
|
|
750
|
+
todo.unshift(i);
|
|
751
|
+
i = findStage(parsed.stages[i].base, i);
|
|
752
|
+
}
|
|
753
|
+
let args = new Map(inherited.get(i));
|
|
754
|
+
for (const at of todo) {
|
|
755
|
+
for (const { kind, pairs, line, onbuild } of parsed.stages[at].instructions) {
|
|
756
|
+
if (kind !== "ARG" || onbuild)
|
|
757
|
+
continue;
|
|
758
|
+
for (const { name: arg, value } of pairs) {
|
|
759
|
+
named("ARG", arg, line);
|
|
760
|
+
const given = buildArg(arg);
|
|
761
|
+
if (given !== undefined)
|
|
762
|
+
args.set(arg, given);
|
|
763
|
+
else if (INJECTED.test(arg))
|
|
764
|
+
args.set(arg, new Unknown("injected"));
|
|
765
|
+
else if (isBuiltin(arg))
|
|
766
|
+
args.set(arg, globals.get(arg));
|
|
767
|
+
else if (value?.includes("$"))
|
|
768
|
+
args.set(arg, new Unknown("error", `ce does not work out the $ in the ARG ${arg} on line ${line}, which a stage built from it gets`));
|
|
769
|
+
else if (value !== undefined)
|
|
770
|
+
args.set(arg, evaluate(value, line, () => {
|
|
771
|
+
return;
|
|
772
|
+
}));
|
|
773
|
+
else if (globals.get(arg) !== undefined)
|
|
774
|
+
args.set(arg, globals.get(arg));
|
|
775
|
+
else if (!args.has(arg))
|
|
776
|
+
args.set(arg, undefined);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
inherited.set(at, args);
|
|
780
|
+
args = new Map(args);
|
|
781
|
+
}
|
|
782
|
+
return args;
|
|
783
|
+
};
|
|
784
|
+
let index = parsed.stages.length - 1;
|
|
785
|
+
if (index < 0)
|
|
786
|
+
fail("it has no FROM");
|
|
787
|
+
if (target) {
|
|
788
|
+
index = findStage(target, parsed.stages.length, true);
|
|
789
|
+
if (index < 0)
|
|
790
|
+
fail(`it has no stage named ${JSON.stringify(target)}`);
|
|
791
|
+
}
|
|
792
|
+
const swapped = replaced(index);
|
|
793
|
+
if (swapped !== undefined)
|
|
794
|
+
fail(`--build-context ${swapped} replaces the stage built`);
|
|
795
|
+
const ancestors = [];
|
|
796
|
+
for (let i = index;i >= 0; i = source(i).parent)
|
|
797
|
+
ancestors.unshift(i);
|
|
798
|
+
for (const i of ancestors)
|
|
799
|
+
run(i);
|
|
800
|
+
const state = run(index);
|
|
801
|
+
const v = state.args.has(name) ? state.args.get(name) : state.undeclared ?? (!globals.has(name) && INJECTED.test(name) && buildArg(name) === undefined ? new Unknown("injected") : globals.get(name));
|
|
802
|
+
if (v instanceof Unknown) {
|
|
803
|
+
if (v.why === "builtin")
|
|
804
|
+
fail(`${name} is set by the builder`);
|
|
805
|
+
if (v.why === "injected")
|
|
806
|
+
fail(`${name} may be set by the builder`);
|
|
807
|
+
fail(v.why === "error" ? v.reason : `ce cannot tell what ${name} is`);
|
|
808
|
+
}
|
|
809
|
+
return v;
|
|
810
|
+
}
|
|
811
|
+
function readContainerfile(file, model) {
|
|
812
|
+
let text;
|
|
813
|
+
try {
|
|
814
|
+
text = fs.readFileSync(file, "utf8");
|
|
815
|
+
} catch (exc) {
|
|
816
|
+
return fail(`ce cannot read ${JSON.stringify(file)}: ${errorMessage(exc)}`);
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
return parseContainerfile(text, model);
|
|
820
|
+
} catch (exc) {
|
|
821
|
+
if (exc instanceof Unreadable)
|
|
822
|
+
fail(`${JSON.stringify(file)}: ${exc.message}`);
|
|
823
|
+
throw exc;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
function containerfileDefaults(files, buildArg) {
|
|
827
|
+
const docker = files.engine === "docker";
|
|
828
|
+
const model = docker ? "buildkit" : "imagebuilder";
|
|
829
|
+
let read;
|
|
830
|
+
const load = () => {
|
|
831
|
+
let file;
|
|
832
|
+
let parsed;
|
|
833
|
+
try {
|
|
834
|
+
file = findContainerfile(files);
|
|
835
|
+
parsed = readContainerfile(file, model);
|
|
836
|
+
} catch (exc) {
|
|
837
|
+
if (exc instanceof Unreadable)
|
|
838
|
+
return { error: exc };
|
|
839
|
+
throw exc;
|
|
840
|
+
}
|
|
841
|
+
const frontend = docker ? buildArg("BUILDKIT_SYNTAX")?.trim().split(" ")[0] ?? parsed.syntax : undefined;
|
|
842
|
+
if (frontend !== undefined && !STANDARD_SYNTAX.test(frontend))
|
|
843
|
+
return {
|
|
844
|
+
file,
|
|
845
|
+
error: new Unreadable(`it is read by the frontend ${JSON.stringify(frontend)}, which may read it differently`)
|
|
846
|
+
};
|
|
847
|
+
return { file, parsed };
|
|
848
|
+
};
|
|
849
|
+
return (name) => {
|
|
850
|
+
read ??= load();
|
|
851
|
+
try {
|
|
852
|
+
if (read.error)
|
|
853
|
+
throw read.error;
|
|
854
|
+
return argDefault(read.parsed, name, files.target, buildArg, model, {
|
|
855
|
+
contexts: files.contexts ?? [],
|
|
856
|
+
envs: files.envs ?? []
|
|
857
|
+
});
|
|
858
|
+
} catch (exc) {
|
|
859
|
+
if (!(exc instanceof Unreadable))
|
|
860
|
+
throw exc;
|
|
861
|
+
const where = read.file ? ` from ${JSON.stringify(read.file)}` : "";
|
|
862
|
+
throw new CeError(`ce: {arg.${name}} has no value from --build-arg, and ce cannot work out its default${where}: ${exc.message}`);
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
|
|
128
867
|
// src/json.ts
|
|
129
868
|
import { randomUUID } from "node:crypto";
|
|
130
|
-
import
|
|
131
|
-
import
|
|
869
|
+
import fs2 from "node:fs";
|
|
870
|
+
import path2 from "node:path";
|
|
132
871
|
function isObject(value) {
|
|
133
872
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
134
873
|
}
|
|
135
874
|
function readJsonFile(file, fallback) {
|
|
136
875
|
try {
|
|
137
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(
|
|
876
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(fs2.readFileSync(file)));
|
|
138
877
|
} catch (exc) {
|
|
139
878
|
if (isErrno(exc, "ENOENT"))
|
|
140
879
|
return fallback;
|
|
@@ -145,22 +884,22 @@ function dumpJson(payload) {
|
|
|
145
884
|
return JSON.stringify(payload, (_key, value) => isObject(value) ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) : value);
|
|
146
885
|
}
|
|
147
886
|
function writePrivateJson(file, payload) {
|
|
148
|
-
const directory =
|
|
149
|
-
|
|
150
|
-
const temporary =
|
|
151
|
-
const fd =
|
|
887
|
+
const directory = path2.dirname(file);
|
|
888
|
+
fs2.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
889
|
+
const temporary = path2.join(directory, `.ce-forgejo-${randomUUID().slice(0, 8)}`);
|
|
890
|
+
const fd = fs2.openSync(temporary, "wx", 384);
|
|
152
891
|
try {
|
|
153
892
|
try {
|
|
154
|
-
|
|
155
|
-
|
|
893
|
+
fs2.fchmodSync(fd, 384);
|
|
894
|
+
fs2.writeSync(fd, `${dumpJson(payload)}
|
|
156
895
|
`);
|
|
157
|
-
|
|
896
|
+
fs2.fsyncSync(fd);
|
|
158
897
|
} finally {
|
|
159
|
-
|
|
898
|
+
fs2.closeSync(fd);
|
|
160
899
|
}
|
|
161
|
-
|
|
900
|
+
fs2.renameSync(temporary, file);
|
|
162
901
|
} finally {
|
|
163
|
-
|
|
902
|
+
fs2.rmSync(temporary, { force: true });
|
|
164
903
|
}
|
|
165
904
|
}
|
|
166
905
|
|
|
@@ -341,7 +1080,10 @@ class CiMetadata {
|
|
|
341
1080
|
const pull = /^refs\/pull\/(\d+)\//u.exec(ref);
|
|
342
1081
|
if (event.startsWith("pull_request") || pull) {
|
|
343
1082
|
const payload = pull ? {} : this.#payload();
|
|
344
|
-
const fromPayload = [
|
|
1083
|
+
const fromPayload = [
|
|
1084
|
+
isObject(payload.pull_request) && payload.pull_request.number,
|
|
1085
|
+
payload.number
|
|
1086
|
+
].find(Number.isInteger);
|
|
345
1087
|
const number = pull?.[1] ?? (fromPayload === undefined ? undefined : String(fromPayload));
|
|
346
1088
|
if (!number)
|
|
347
1089
|
throw new CeError(`ce: cannot find the pull request number for ${event} on ${ref}`);
|
|
@@ -395,20 +1137,20 @@ function onDefaultBranch(vars, branch) {
|
|
|
395
1137
|
if (UNKNOWN_ON_DEFAULT_BRANCH.has(name)) {
|
|
396
1138
|
throw new CeError(`ce: the default branch's cache scope cannot use {${name}}: this run does not know its commit`);
|
|
397
1139
|
}
|
|
398
|
-
return name === "image" || name.startsWith("env.")
|
|
1140
|
+
return name === "image" || name.startsWith("env.") ? vars.get(name) : undefined;
|
|
399
1141
|
}
|
|
400
1142
|
};
|
|
401
1143
|
}
|
|
402
1144
|
|
|
403
1145
|
// src/system.ts
|
|
404
1146
|
import { spawnSync } from "node:child_process";
|
|
405
|
-
import
|
|
406
|
-
import
|
|
1147
|
+
import fs3 from "node:fs";
|
|
1148
|
+
import path3 from "node:path";
|
|
407
1149
|
import { setTimeout } from "node:timers/promises";
|
|
408
1150
|
var io = {
|
|
409
|
-
stdout: (text) => void
|
|
410
|
-
stderr: (text) => void
|
|
411
|
-
stdin: () =>
|
|
1151
|
+
stdout: (text) => void fs3.writeSync(1, text),
|
|
1152
|
+
stderr: (text) => void fs3.writeSync(2, text),
|
|
1153
|
+
stdin: () => fs3.readFileSync(0, "utf8"),
|
|
412
1154
|
argv0: () => process.argv[1] ?? "ce"
|
|
413
1155
|
};
|
|
414
1156
|
function log(line) {
|
|
@@ -423,11 +1165,11 @@ function childEnv() {
|
|
|
423
1165
|
return Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined));
|
|
424
1166
|
}
|
|
425
1167
|
function which(command) {
|
|
426
|
-
const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(
|
|
1168
|
+
const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(path3.delimiter).filter(Boolean).map((dir) => path3.join(dir, command));
|
|
427
1169
|
for (const candidate of candidates) {
|
|
428
1170
|
try {
|
|
429
|
-
|
|
430
|
-
if (
|
|
1171
|
+
fs3.accessSync(candidate, fs3.constants.X_OK);
|
|
1172
|
+
if (fs3.statSync(candidate).isFile())
|
|
431
1173
|
return candidate;
|
|
432
1174
|
} catch {}
|
|
433
1175
|
}
|
|
@@ -459,13 +1201,13 @@ var system = {
|
|
|
459
1201
|
}
|
|
460
1202
|
};
|
|
461
1203
|
function binDir() {
|
|
462
|
-
return
|
|
1204
|
+
return fs3.realpathSync(path3.join(import.meta.dirname, "..", "bin"));
|
|
463
1205
|
}
|
|
464
1206
|
function ensureHelperOnPath() {
|
|
465
1207
|
const dir = binDir();
|
|
466
|
-
const parts = (process.env.PATH ?? "").split(
|
|
1208
|
+
const parts = (process.env.PATH ?? "").split(path3.delimiter);
|
|
467
1209
|
if (!parts.includes(dir))
|
|
468
|
-
process.env.PATH = [dir, ...parts].join(
|
|
1210
|
+
process.env.PATH = [dir, ...parts].join(path3.delimiter);
|
|
469
1211
|
}
|
|
470
1212
|
|
|
471
1213
|
// src/template.ts
|
|
@@ -758,7 +1500,7 @@ var SUMMARIZED = {
|
|
|
758
1500
|
"--label": "labels"
|
|
759
1501
|
};
|
|
760
1502
|
var ARG_VARIABLE = /^arg\.([A-Za-z_][A-Za-z0-9_]*)$/u;
|
|
761
|
-
function withBuildArgs(vars, values, argFile) {
|
|
1503
|
+
function withBuildArgs(vars, values, argFile, fromFile) {
|
|
762
1504
|
return {
|
|
763
1505
|
known: (name) => ARG_VARIABLE.test(name) || vars.known(name),
|
|
764
1506
|
get(name) {
|
|
@@ -766,17 +1508,40 @@ function withBuildArgs(vars, values, argFile) {
|
|
|
766
1508
|
if (arg === undefined)
|
|
767
1509
|
return vars.get(name);
|
|
768
1510
|
const value = values.get(arg);
|
|
769
|
-
if (value ===
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
throw new CeError(`ce: {${name}} needs --build-arg ${arg}=<value> on this command, or --build-arg ${arg} with ${arg} set; ` + "ce does not read defaults from the Containerfile");
|
|
1511
|
+
if (typeof value === "string")
|
|
1512
|
+
return value || undefined;
|
|
1513
|
+
if (argFile) {
|
|
1514
|
+
throw new CeError(`ce: {${name}} has no value from --build-arg, and may get one from --build-arg-file, which ce does not read`);
|
|
774
1515
|
}
|
|
775
|
-
return
|
|
1516
|
+
return fromFile(arg) || undefined;
|
|
776
1517
|
}
|
|
777
1518
|
};
|
|
778
1519
|
}
|
|
779
|
-
|
|
1520
|
+
var SHORT_BOOLS = {
|
|
1521
|
+
docker: "qD",
|
|
1522
|
+
podman: "qD",
|
|
1523
|
+
buildah: "qD"
|
|
1524
|
+
};
|
|
1525
|
+
function ungroup(argv, engine) {
|
|
1526
|
+
const end = argv.indexOf("--");
|
|
1527
|
+
return argv.flatMap((arg, at) => {
|
|
1528
|
+
if (end >= 0 && at >= end || !/^-[^-]/u.test(arg))
|
|
1529
|
+
return [arg];
|
|
1530
|
+
const split = [];
|
|
1531
|
+
let i = 1;
|
|
1532
|
+
while (i < arg.length - 1 && SHORT_BOOLS[engine].includes(arg[i]) && arg[i + 1] !== "=")
|
|
1533
|
+
split.push(`-${arg[i++]}`);
|
|
1534
|
+
return i === 1 ? [arg] : [...split, `-${arg.slice(i)}`];
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
function parseBuildArgs(given, tagPrefix, cachePrefix, {
|
|
1538
|
+
vars = new CiMetadata,
|
|
1539
|
+
labels = {},
|
|
1540
|
+
defaultBranch,
|
|
1541
|
+
env = process.env,
|
|
1542
|
+
engine = "docker"
|
|
1543
|
+
} = {}) {
|
|
1544
|
+
const argv = ungroup(given, engine);
|
|
780
1545
|
const parts = [];
|
|
781
1546
|
const rawCacheFrom = [];
|
|
782
1547
|
const rawCacheTo = [];
|
|
@@ -794,6 +1559,10 @@ function parseBuildArgs(argv, tagPrefix, cachePrefix, { vars = new CiMetadata, l
|
|
|
794
1559
|
let templated = false;
|
|
795
1560
|
const buildArgs = new Map;
|
|
796
1561
|
let argFile = false;
|
|
1562
|
+
const files = [];
|
|
1563
|
+
const positionals = [];
|
|
1564
|
+
const contexts = [];
|
|
1565
|
+
const envs = [];
|
|
797
1566
|
let rest = [];
|
|
798
1567
|
const addTags = (value, flag) => {
|
|
799
1568
|
templated ||= hasPlaceholders(value);
|
|
@@ -808,7 +1577,11 @@ function parseBuildArgs(argv, tagPrefix, cachePrefix, { vars = new CiMetadata, l
|
|
|
808
1577
|
buildArgs.set(value, env[value]);
|
|
809
1578
|
else if (engine !== "docker")
|
|
810
1579
|
buildArgs.set(value, null);
|
|
1580
|
+
if (engine !== "docker" && buildArgs.has("TARGETPLATFORM"))
|
|
1581
|
+
buildArgs.set("TARGETPLATFORM", null);
|
|
811
1582
|
}
|
|
1583
|
+
if (field === "dockerfile")
|
|
1584
|
+
files.push(value);
|
|
812
1585
|
if (field === "buildArgs" || field === "labels")
|
|
813
1586
|
summary[field].push(value);
|
|
814
1587
|
else
|
|
@@ -818,8 +1591,10 @@ function parseBuildArgs(argv, tagPrefix, cachePrefix, { vars = new CiMetadata, l
|
|
|
818
1591
|
const arg = argv[i];
|
|
819
1592
|
const [name, attached] = arg.startsWith("--") && arg.includes("=") ? splitAt(arg, "=") : [arg, undefined];
|
|
820
1593
|
const next = () => consumeValue(argv.slice(++i), arg)[0];
|
|
1594
|
+
const short = () => arg.slice(arg[2] === "=" ? 3 : 2);
|
|
821
1595
|
if (arg === "--") {
|
|
822
1596
|
rest = argv.slice(i);
|
|
1597
|
+
positionals.push(...rest.slice(1));
|
|
823
1598
|
if (rest.length > 1)
|
|
824
1599
|
summary.context = rest.at(-1);
|
|
825
1600
|
break;
|
|
@@ -828,7 +1603,7 @@ function parseBuildArgs(argv, tagPrefix, cachePrefix, { vars = new CiMetadata, l
|
|
|
828
1603
|
else if (arg === "--tag")
|
|
829
1604
|
addTags(next(), "--tag=");
|
|
830
1605
|
else if (arg.startsWith("-t"))
|
|
831
|
-
addTags(
|
|
1606
|
+
addTags(short(), "-t");
|
|
832
1607
|
else if (name === "--tag")
|
|
833
1608
|
addTags(attached, "--tag=");
|
|
834
1609
|
else if (name === "--cache-from")
|
|
@@ -854,16 +1629,33 @@ function parseBuildArgs(argv, tagPrefix, cachePrefix, { vars = new CiMetadata, l
|
|
|
854
1629
|
parts.push(arg);
|
|
855
1630
|
if (attached === undefined)
|
|
856
1631
|
parts.push(next());
|
|
1632
|
+
} else if (name === "--build-context") {
|
|
1633
|
+
const v = attached ?? next();
|
|
1634
|
+
parts.push(arg);
|
|
1635
|
+
if (attached === undefined)
|
|
1636
|
+
parts.push(v);
|
|
1637
|
+
contexts.push(v.split("=")[0]);
|
|
1638
|
+
} else if ((name === "--env" || name === "--unsetenv") && engine !== "docker") {
|
|
1639
|
+
const v = attached ?? next();
|
|
1640
|
+
parts.push(arg);
|
|
1641
|
+
if (attached === undefined)
|
|
1642
|
+
parts.push(v);
|
|
1643
|
+
envs.push(`${name} ${v}`);
|
|
857
1644
|
} else if (arg.startsWith("-f") && arg.length > 2) {
|
|
858
1645
|
parts.push(arg);
|
|
859
|
-
summary.dockerfile =
|
|
1646
|
+
summary.dockerfile = short();
|
|
1647
|
+
files.push(summary.dockerfile);
|
|
860
1648
|
} else {
|
|
861
1649
|
parts.push(arg);
|
|
862
|
-
if (!arg.startsWith("-"))
|
|
1650
|
+
if (!arg.startsWith("-")) {
|
|
1651
|
+
positionals.push(arg);
|
|
863
1652
|
summary.context = arg;
|
|
1653
|
+
}
|
|
864
1654
|
}
|
|
865
1655
|
}
|
|
866
|
-
|
|
1656
|
+
const baseVars = vars;
|
|
1657
|
+
const fromFile = containerfileDefaults({ engine, files, positionals, target: summary.target, contexts, envs }, (arg) => buildArgs.get(arg) ?? undefined);
|
|
1658
|
+
vars = withBuildArgs(baseVars, buildArgs, argFile, fromFile);
|
|
867
1659
|
const args = parts.flatMap((part) => {
|
|
868
1660
|
if (typeof part === "string")
|
|
869
1661
|
return [part];
|
|
@@ -885,7 +1677,11 @@ function parseBuildArgs(argv, tagPrefix, cachePrefix, { vars = new CiMetadata, l
|
|
|
885
1677
|
const cacheFrom = rawCacheFrom.map((c) => toNames.has(cacheScopeName(c)) ? normalizeCacheScope(c, cachePrefix) : c);
|
|
886
1678
|
const ghaTo = rawCacheTo.filter(isGhaCache);
|
|
887
1679
|
if (defaultBranch && ghaTo.length) {
|
|
888
|
-
const
|
|
1680
|
+
const fileArg = (arg) => {
|
|
1681
|
+
throw new CeError(`ce: the default branch's cache scope cannot use {arg.${arg}} from the Containerfile, which may differ there`);
|
|
1682
|
+
};
|
|
1683
|
+
const onDefault = withBuildArgs(onDefaultBranch(baseVars, defaultBranch()), buildArgs, argFile, fileArg);
|
|
1684
|
+
const prefix = expandPrefix(prefixTemplate, onDefault, "cache prefix");
|
|
889
1685
|
cacheFrom.push(...ghaTo.map((c) => normalizeCacheScope(withoutCacheMode(c), prefix)));
|
|
890
1686
|
}
|
|
891
1687
|
for (const rewritten of new Set(cacheFrom)) {
|
|
@@ -909,7 +1705,11 @@ function printBuildSummary(engine, args, summary) {
|
|
|
909
1705
|
if (!command || command.tagsOnly)
|
|
910
1706
|
return;
|
|
911
1707
|
const subcommand = command.name;
|
|
912
|
-
const out = [
|
|
1708
|
+
const out = [
|
|
1709
|
+
"[ce] build summary:",
|
|
1710
|
+
` engine: ${engine}`,
|
|
1711
|
+
` subcommand: ${subcommand}`
|
|
1712
|
+
];
|
|
913
1713
|
const list = (title, items) => {
|
|
914
1714
|
if (items.length)
|
|
915
1715
|
out.push(` ${title}:`, ...items.map((item) => ` - ${item}`));
|
|
@@ -947,14 +1747,20 @@ async function forgejoOidcJwt(audience, requestUrl, requestToken) {
|
|
|
947
1747
|
throw new Error("credentials in URL");
|
|
948
1748
|
url.searchParams.set("audience", audience);
|
|
949
1749
|
tokenUrl = url.href;
|
|
950
|
-
headers = {
|
|
1750
|
+
headers = {
|
|
1751
|
+
Accept: "application/json",
|
|
1752
|
+
Authorization: `bearer ${requestToken}`
|
|
1753
|
+
};
|
|
951
1754
|
new Headers(headers);
|
|
952
1755
|
} catch {
|
|
953
1756
|
throw new CeError("token request URL or headers are invalid");
|
|
954
1757
|
}
|
|
955
1758
|
let body;
|
|
956
1759
|
try {
|
|
957
|
-
const response = await fetch(tokenUrl, {
|
|
1760
|
+
const response = await fetch(tokenUrl, {
|
|
1761
|
+
headers,
|
|
1762
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
1763
|
+
});
|
|
958
1764
|
if (!response.ok)
|
|
959
1765
|
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
960
1766
|
body = await response.arrayBuffer();
|
|
@@ -995,20 +1801,20 @@ function needsRefresh(expiry) {
|
|
|
995
1801
|
}
|
|
996
1802
|
|
|
997
1803
|
// src/state.ts
|
|
998
|
-
import
|
|
1804
|
+
import fs5 from "node:fs";
|
|
999
1805
|
import { homedir } from "node:os";
|
|
1000
|
-
import
|
|
1806
|
+
import path4 from "node:path";
|
|
1001
1807
|
|
|
1002
1808
|
// src/lock.ts
|
|
1003
|
-
import
|
|
1809
|
+
import fs4 from "node:fs";
|
|
1004
1810
|
import { hostname } from "node:os";
|
|
1005
1811
|
import { setTimeout as setTimeout2 } from "node:timers/promises";
|
|
1006
1812
|
var STALE_AFTER_MS = 120000;
|
|
1007
1813
|
var POLL_MS = 50;
|
|
1008
1814
|
function snapshot(lockPath) {
|
|
1009
1815
|
try {
|
|
1010
|
-
const { ino, mtimeMs } =
|
|
1011
|
-
return { ino, mtimeMs, owner:
|
|
1816
|
+
const { ino, mtimeMs } = fs4.statSync(lockPath);
|
|
1817
|
+
return { ino, mtimeMs, owner: fs4.readFileSync(lockPath, "utf8").trim() };
|
|
1012
1818
|
} catch (exc) {
|
|
1013
1819
|
if (isErrno(exc, "ENOENT"))
|
|
1014
1820
|
return null;
|
|
@@ -1034,7 +1840,7 @@ function breakIfStale(lockPath) {
|
|
|
1034
1840
|
return;
|
|
1035
1841
|
const guard = `${lockPath}.break`;
|
|
1036
1842
|
try {
|
|
1037
|
-
|
|
1843
|
+
fs4.writeFileSync(guard, "", { flag: "wx", mode: 384 });
|
|
1038
1844
|
} catch (exc) {
|
|
1039
1845
|
if (!isErrno(exc, "EEXIST"))
|
|
1040
1846
|
throw exc;
|
|
@@ -1047,10 +1853,10 @@ function breakIfStale(lockPath) {
|
|
|
1047
1853
|
try {
|
|
1048
1854
|
const current = snapshot(lockPath);
|
|
1049
1855
|
if (current && current.ino === stale.ino && current.mtimeMs === stale.mtimeMs && current.owner === stale.owner) {
|
|
1050
|
-
|
|
1856
|
+
fs4.rmSync(lockPath, { force: true });
|
|
1051
1857
|
}
|
|
1052
1858
|
} finally {
|
|
1053
|
-
|
|
1859
|
+
fs4.rmSync(guard, { force: true });
|
|
1054
1860
|
}
|
|
1055
1861
|
}
|
|
1056
1862
|
async function withFileLock(lockPath, fn) {
|
|
@@ -1059,7 +1865,7 @@ async function withFileLock(lockPath, fn) {
|
|
|
1059
1865
|
for (;; ) {
|
|
1060
1866
|
let fd;
|
|
1061
1867
|
try {
|
|
1062
|
-
fd =
|
|
1868
|
+
fd = fs4.openSync(lockPath, "wx", 384);
|
|
1063
1869
|
} catch (exc) {
|
|
1064
1870
|
if (!isErrno(exc, "EEXIST"))
|
|
1065
1871
|
throw exc;
|
|
@@ -1068,11 +1874,11 @@ async function withFileLock(lockPath, fn) {
|
|
|
1068
1874
|
continue;
|
|
1069
1875
|
}
|
|
1070
1876
|
try {
|
|
1071
|
-
|
|
1877
|
+
fs4.writeSync(fd, `${owner}
|
|
1072
1878
|
`);
|
|
1073
|
-
ino =
|
|
1879
|
+
ino = fs4.fstatSync(fd).ino;
|
|
1074
1880
|
} finally {
|
|
1075
|
-
|
|
1881
|
+
fs4.closeSync(fd);
|
|
1076
1882
|
}
|
|
1077
1883
|
break;
|
|
1078
1884
|
}
|
|
@@ -1081,7 +1887,7 @@ async function withFileLock(lockPath, fn) {
|
|
|
1081
1887
|
} finally {
|
|
1082
1888
|
const current = snapshot(lockPath);
|
|
1083
1889
|
if (current?.ino === ino && current.owner === owner)
|
|
1084
|
-
|
|
1890
|
+
fs4.rmSync(lockPath, { force: true });
|
|
1085
1891
|
}
|
|
1086
1892
|
}
|
|
1087
1893
|
|
|
@@ -1091,16 +1897,16 @@ var STATE_FILE = "ce-forgejo-credentials.json";
|
|
|
1091
1897
|
function dockerConfigDir() {
|
|
1092
1898
|
const configured = process.env.DOCKER_CONFIG;
|
|
1093
1899
|
if (!configured)
|
|
1094
|
-
return
|
|
1095
|
-
const expanded = configured === "~" || configured.startsWith("~/") ?
|
|
1096
|
-
return
|
|
1900
|
+
return path4.join(homedir(), ".docker");
|
|
1901
|
+
const expanded = configured === "~" || configured.startsWith("~/") ? path4.join(homedir(), configured.slice(1)) : configured;
|
|
1902
|
+
return path4.resolve(expanded);
|
|
1097
1903
|
}
|
|
1098
1904
|
function helperStatePath() {
|
|
1099
|
-
return
|
|
1905
|
+
return path4.join(dockerConfigDir(), STATE_FILE);
|
|
1100
1906
|
}
|
|
1101
1907
|
async function withHelperState(fn) {
|
|
1102
1908
|
const statePath = helperStatePath();
|
|
1103
|
-
|
|
1909
|
+
fs5.mkdirSync(path4.dirname(statePath), { recursive: true, mode: 448 });
|
|
1104
1910
|
return withFileLock(`${statePath}.pidlock`, async () => {
|
|
1105
1911
|
const state = readJsonFile(statePath, { version: 1, registries: {} });
|
|
1106
1912
|
if (!isObject(state) || !isObject(state.registries)) {
|
|
@@ -1129,7 +1935,7 @@ async function saveHelperState(registry, username, audience, authority, jwt) {
|
|
|
1129
1935
|
}
|
|
1130
1936
|
async function configureDockerCredentialHelper(registry, username, audience, authority, jwt) {
|
|
1131
1937
|
await saveHelperState(registry, username, audience, authority, jwt);
|
|
1132
|
-
const configPath =
|
|
1938
|
+
const configPath = path4.join(dockerConfigDir(), "config.json");
|
|
1133
1939
|
const config = readJsonFile(configPath, {});
|
|
1134
1940
|
if (!isObject(config))
|
|
1135
1941
|
throw new CeError("Docker config is not a JSON object");
|
|
@@ -1258,16 +2064,6 @@ function envFlag(name) {
|
|
|
1258
2064
|
function shellQuote(arg) {
|
|
1259
2065
|
return /^[A-Za-z0-9_@%+:,./-][A-Za-z0-9_@%+=:,./-]*$/u.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`;
|
|
1260
2066
|
}
|
|
1261
|
-
function engineCommand(engine, args) {
|
|
1262
|
-
if (engine === "docker" || !system.which("gosu"))
|
|
1263
|
-
return [engine, ...args];
|
|
1264
|
-
const user = process.env.USER ?? "";
|
|
1265
|
-
if (!user) {
|
|
1266
|
-
logError("ce: gosu is available but USER is not set; cannot drop privileges. Set USER or remove gosu from PATH.");
|
|
1267
|
-
return null;
|
|
1268
|
-
}
|
|
1269
|
-
return ["gosu", user, engine, ...args];
|
|
1270
|
-
}
|
|
1271
2067
|
async function runEngine(engine, argv, tagPrefix, cachePrefix, options = {}) {
|
|
1272
2068
|
const dryRun = options.dryRun ?? envFlag("CE_DRY_RUN");
|
|
1273
2069
|
ensureHelperOnPath();
|
|
@@ -1288,14 +2084,11 @@ async function runEngine(engine, argv, tagPrefix, cachePrefix, options = {}) {
|
|
|
1288
2084
|
summary = parsed.summary;
|
|
1289
2085
|
}
|
|
1290
2086
|
if (dryRun) {
|
|
1291
|
-
const run = engineCommand(engine, args);
|
|
1292
|
-
if (!run)
|
|
1293
|
-
return 1;
|
|
1294
2087
|
if (summary)
|
|
1295
2088
|
printBuildSummary(engine, args, summary);
|
|
1296
2089
|
if (!system.which(engine))
|
|
1297
2090
|
logError(`ce: warning: ${engine} is not on PATH, so running this here would fail`);
|
|
1298
|
-
io.stdout(`${
|
|
2091
|
+
io.stdout(`${[engine, ...args].map(shellQuote).join(" ")}
|
|
1299
2092
|
`);
|
|
1300
2093
|
return 0;
|
|
1301
2094
|
}
|
|
@@ -1307,8 +2100,7 @@ async function runEngine(engine, argv, tagPrefix, cachePrefix, options = {}) {
|
|
|
1307
2100
|
return 1;
|
|
1308
2101
|
if (summary)
|
|
1309
2102
|
printBuildSummary(engine, args, summary);
|
|
1310
|
-
|
|
1311
|
-
return run ? system.exec(run[0], run) : 1;
|
|
2103
|
+
return system.exec(engine, [engine, ...args]);
|
|
1312
2104
|
}
|
|
1313
2105
|
|
|
1314
2106
|
// src/login.ts
|
|
@@ -1327,13 +2119,21 @@ function oidcConfig() {
|
|
|
1327
2119
|
return null;
|
|
1328
2120
|
const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL ?? "";
|
|
1329
2121
|
const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ?? "";
|
|
1330
|
-
const missing = [
|
|
2122
|
+
const missing = [
|
|
2123
|
+
"ACTIONS_ID_TOKEN_REQUEST_URL",
|
|
2124
|
+
"ACTIONS_ID_TOKEN_REQUEST_TOKEN"
|
|
2125
|
+
].filter((name) => !process.env[name]);
|
|
1331
2126
|
if (missing.length) {
|
|
1332
2127
|
return `Forgejo OIDC login config is incomplete: AUD is set, but ${missing.map(unsetOrEmpty).join("; ")}`;
|
|
1333
2128
|
}
|
|
1334
2129
|
try {
|
|
1335
2130
|
assertAbsoluteHttpUrl(requestUrl, "must be an absolute HTTP(S) URL");
|
|
1336
|
-
return {
|
|
2131
|
+
return {
|
|
2132
|
+
audience,
|
|
2133
|
+
requestUrl,
|
|
2134
|
+
requestToken,
|
|
2135
|
+
authority: urlAuthority(requestUrl)
|
|
2136
|
+
};
|
|
1337
2137
|
} catch (exc) {
|
|
1338
2138
|
if (!(exc instanceof CeError))
|
|
1339
2139
|
throw exc;
|
|
@@ -1346,7 +2146,14 @@ function loginWithEngines(target, password) {
|
|
|
1346
2146
|
continue;
|
|
1347
2147
|
let result;
|
|
1348
2148
|
try {
|
|
1349
|
-
result = system.run([
|
|
2149
|
+
result = system.run([
|
|
2150
|
+
engine,
|
|
2151
|
+
"login",
|
|
2152
|
+
"-u",
|
|
2153
|
+
target.user,
|
|
2154
|
+
"--password-stdin",
|
|
2155
|
+
target.registry
|
|
2156
|
+
], `${password}
|
|
1350
2157
|
`);
|
|
1351
2158
|
} catch (exc) {
|
|
1352
2159
|
if (isErrno(exc, "ENOENT"))
|
|
@@ -1389,7 +2196,10 @@ async function login() {
|
|
|
1389
2196
|
try {
|
|
1390
2197
|
const authority = urlAuthority(rawRegistry);
|
|
1391
2198
|
const registry = registryServer(rawRegistry);
|
|
1392
|
-
registries.push({
|
|
2199
|
+
registries.push({
|
|
2200
|
+
registry,
|
|
2201
|
+
useOidc: oidc !== null && sameAuthority(authority, oidc.authority)
|
|
2202
|
+
});
|
|
1393
2203
|
} catch (exc) {
|
|
1394
2204
|
if (!(exc instanceof CeError))
|
|
1395
2205
|
throw exc;
|
|
@@ -1510,9 +2320,9 @@ Placeholders, in build tags and in the tag and cache prefixes:
|
|
|
1510
2320
|
From a SemVer tag such as v1.2.3 (v optional)
|
|
1511
2321
|
{stable} Set only for a SemVer tag without a pre-release
|
|
1512
2322
|
{env.NAME} The environment variable NAME
|
|
1513
|
-
{arg.NAME} The build arg NAME
|
|
1514
|
-
|
|
1515
|
-
|
|
2323
|
+
{arg.NAME} The build arg NAME as the build gets it: from
|
|
2324
|
+
--build-arg, else the Containerfile's default for the
|
|
2325
|
+
stage built (--target's or the last)
|
|
1516
2326
|
A guard that does not pass leaves its tag out or its prefix empty even if
|
|
1517
2327
|
another guard there cannot be worked out.
|
|
1518
2328
|
Values are made safe for a tag ("ci/x" becomes "ci-x"). Quote templates
|
|
@@ -1524,8 +2334,8 @@ Commands:
|
|
|
1524
2334
|
|
|
1525
2335
|
Engines:
|
|
1526
2336
|
docker [args] Passthrough to docker
|
|
1527
|
-
podman [args] Passthrough to podman
|
|
1528
|
-
buildah [args] Passthrough to buildah
|
|
2337
|
+
podman [args] Passthrough to podman
|
|
2338
|
+
buildah [args] Passthrough to buildah
|
|
1529
2339
|
|
|
1530
2340
|
Environment variables (used by login):
|
|
1531
2341
|
Each registry is a "group". The default group uses the bare variables
|