67 lines
2.4 KiB
JavaScript
67 lines
2.4 KiB
JavaScript
#!/usr/bin/env node
|
|
import { execFileSync } from "node:child_process";
|
|
import { appendFileSync } from "node:fs";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
export function cloudCacheCandidates(image, commits) {
|
|
if (!/^ghcr\.io\/[a-z0-9._-]+\/[a-z0-9._-]+$/.test(image ?? "")) {
|
|
throw new Error("Expected a GHCR owner/repository cache image.");
|
|
}
|
|
if (!Array.isArray(commits) || commits.length === 0 || commits.some((sha) => !/^[a-f0-9]{40}$/.test(sha))) {
|
|
throw new Error("Cloud cache ancestry requires full commit SHAs.");
|
|
}
|
|
return [
|
|
...[...new Set(commits)].slice(0, 10).map((sha) => `${image}:buildcache-cloud-${sha}`),
|
|
`${image}:buildcache-cloud`,
|
|
];
|
|
}
|
|
|
|
export async function selectCloudCache(image, commits, {
|
|
exists = registryCacheExists,
|
|
log = console.log,
|
|
} = {}) {
|
|
for (const ref of cloudCacheCandidates(image, commits)) {
|
|
try {
|
|
if (!await exists(ref)) continue;
|
|
log(`Using cloud cache: ${ref}`);
|
|
return `type=registry,ref=${ref}`;
|
|
} catch {
|
|
// Cache availability must not turn an otherwise valid build into a
|
|
// failure. A later ancestor may still be available during a rollout.
|
|
log(`Could not inspect cloud cache ${ref}; trying the next ancestor.`);
|
|
}
|
|
}
|
|
log("No cloud cache is available; this build will populate one.");
|
|
return "";
|
|
}
|
|
|
|
function registryCacheExists(ref) {
|
|
try {
|
|
// Use the preceding Docker login, including for private registry caches.
|
|
// Inspect metadata only: no layer download and no image execution.
|
|
execFileSync("docker", ["buildx", "imagetools", "inspect", "--raw", ref], {
|
|
timeout: 10_000,
|
|
maxBuffer: 1024 * 1024,
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
return true;
|
|
} catch (error) {
|
|
if (/manifest unknown|not found|NAME_UNKNOWN/i.test(String(error.stderr ?? ""))) return false;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
try {
|
|
if (!process.env.GITHUB_OUTPUT) throw new Error("GITHUB_OUTPUT is required.");
|
|
const commits = execFileSync("git", ["rev-list", "--first-parent", "--max-count=10", "HEAD"], {
|
|
encoding: "utf8",
|
|
}).trim().split("\n");
|
|
const source = await selectCloudCache(process.env.CACHE_IMAGE, commits, { exists: registryCacheExists });
|
|
appendFileSync(process.env.GITHUB_OUTPUT, `source=${source}\n`);
|
|
} catch (error) {
|
|
console.error(error.message);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|