ci(docker): reuse cached e2e images for reruns

This commit is contained in:
Peter Steinberger
2026-04-27 06:29:03 +01:00
parent 679e476183
commit 5e9a96fafb
11 changed files with 319 additions and 63 deletions

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env node
// Builds cheap rerun commands from a Docker E2E GitHub run or local summary.
// For GitHub runs, the script downloads Docker E2E artifacts, reads
// summary/failures JSON, and prints targeted workflow commands that prepare a
// fresh OpenClaw tarball for the same ref before running only failed lanes.
// summary/failures JSON, and prints targeted workflow commands for failed
// lanes, reusing package artifacts and prepared GHCR images when artifacts
// expose them.
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
@@ -76,8 +77,44 @@ function shellQuote(value) {
return `'${String(value).replaceAll("'", "'\\''")}'`;
}
function ghWorkflowCommand(lanes, ref, workflow) {
return [
function maybeGhcrImage(value) {
return typeof value === "string" && value.startsWith("ghcr.io/") ? value : "";
}
function reuseInputsFromJson(parsed) {
const packageArtifactRunId = parsed.github?.runId || "";
if (!packageArtifactRunId) {
return {};
}
return {
bareImage: maybeGhcrImage(parsed.images?.bare),
functionalImage: maybeGhcrImage(parsed.images?.functional),
packageArtifactName:
parsed.packageArtifactName || parsed.artifacts?.packageName || "docker-e2e-package",
packageArtifactRunId,
};
}
function sameReuseInputs(left, right) {
return (
(left?.packageArtifactRunId || "") === (right?.packageArtifactRunId || "") &&
(left?.packageArtifactName || "") === (right?.packageArtifactName || "") &&
(left?.bareImage || "") === (right?.bareImage || "") &&
(left?.functionalImage || "") === (right?.functionalImage || "")
);
}
function commonReuseInputs(entries) {
const inputs = entries.map((entry) => entry.reuseInputs).filter(Boolean);
if (inputs.length === 0) {
return {};
}
const [first] = inputs;
return inputs.every((input) => sameReuseInputs(first, input)) ? first : {};
}
function ghWorkflowCommand(lanes, ref, workflow, reuseInputs = {}) {
const fields = [
"gh workflow run",
shellQuote(workflow),
"-f",
@@ -94,7 +131,21 @@ function ghWorkflowCommand(lanes, ref, workflow) {
"include_live_suites=false",
"-f",
"live_models_only=false",
].join(" ");
];
if (reuseInputs.packageArtifactRunId) {
fields.push("-f", `package_artifact_run_id=${shellQuote(reuseInputs.packageArtifactRunId)}`);
fields.push(
"-f",
`package_artifact_name=${shellQuote(reuseInputs.packageArtifactName || "docker-e2e-package")}`,
);
}
if (reuseInputs.bareImage) {
fields.push("-f", `docker_e2e_bare_image=${shellQuote(reuseInputs.bareImage)}`);
}
if (reuseInputs.functionalImage) {
fields.push("-f", `docker_e2e_functional_image=${shellQuote(reuseInputs.functionalImage)}`);
}
return fields.join(" ");
}
function detectRepo() {
@@ -115,15 +166,18 @@ function findFiles(rootDir, basenames, out = []) {
function failedLaneEntriesFromJson(file, ref, workflow) {
const parsed = readJson(file);
const reuseInputs = reuseInputsFromJson(parsed);
const source = path.basename(file);
if (source === "failures.json" && Array.isArray(parsed.lanes)) {
return parsed.lanes
.filter((lane) => lane.name)
.map((lane) => ({
ghWorkflowCommand: lane.ghWorkflowCommand,
ghWorkflowCommand:
lane.ghWorkflowCommand || ghWorkflowCommand([lane.name], ref, workflow, reuseInputs),
lane: lane.name,
localRerunCommand: lane.rerunCommand,
logFile: lane.logFile,
reuseInputs,
source: file,
status: lane.status,
}));
@@ -133,10 +187,11 @@ function failedLaneEntriesFromJson(file, ref, workflow) {
return lanes
.filter((lane) => lane.status !== 0 && lane.name)
.map((lane) => ({
ghWorkflowCommand: ghWorkflowCommand([lane.name], ref, workflow),
ghWorkflowCommand: ghWorkflowCommand([lane.name], ref, workflow, reuseInputs),
lane: lane.name,
localRerunCommand: lane.rerunCommand,
logFile: lane.logFile,
reuseInputs,
source: file,
status: lane.status,
}));
@@ -201,7 +256,7 @@ function printEntries(entries, ref, workflow, run) {
}
console.log(`Ref: ${ref}`);
console.log(
"Targeted GitHub reruns prepare a fresh OpenClaw npm tarball for that ref before lane execution.",
"Targeted GitHub reruns reuse package artifacts and prepared GHCR images when the downloaded artifacts expose them.",
);
if (entries.length === 0) {
console.log("No failed Docker E2E lanes found.");
@@ -215,6 +270,7 @@ function printEntries(entries, ref, workflow, run) {
entries.map((entry) => entry.lane),
ref,
workflow,
commonReuseInputs(entries),
),
);
console.log("");