mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:50:43 +00:00
ci(docker): reuse cached e2e images for reruns
This commit is contained in:
@@ -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("");
|
||||
|
||||
Reference in New Issue
Block a user