paperclip/scripts/select-cloud-cache.mjs

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;
}
}