60 lines
2.3 KiB
TypeScript
60 lines
2.3 KiB
TypeScript
|
|
// lib/versions.ts
|
||
|
|
//
|
||
|
|
// Reads foundation/VERSIONS (the determinism pin-file, T01 / baseline D5) and
|
||
|
|
// resolves an image reference by key. Image refs live in VERSIONS, NOT in Pulumi
|
||
|
|
// config (config.ts comment; CONTRACT_001 §1.1). Components ask for an image by
|
||
|
|
// its logical name; this module returns the pinned `repo:tag@sha256:<digest>`.
|
||
|
|
//
|
||
|
|
// PIN_DIGEST handling (D5 "no floating tags"):
|
||
|
|
// VERSIONS may carry `@sha256:PIN_DIGEST` placeholders until digests are pinned
|
||
|
|
// online (T01 documented the `pin-digests` procedure). For a real deploy that is
|
||
|
|
// a hard error. For local/ephemeral VALIDATION against a dev Docker host, export
|
||
|
|
// FOUNDATION_ALLOW_UNPINNED=1 to fall back to the bare tag (the digest is dropped).
|
||
|
|
import * as fs from "fs";
|
||
|
|
import * as path from "path";
|
||
|
|
|
||
|
|
const VERSIONS_PATH = path.resolve(__dirname, "..", "..", "VERSIONS");
|
||
|
|
|
||
|
|
let cache: Record<string, string> | undefined;
|
||
|
|
|
||
|
|
function load(): Record<string, string> {
|
||
|
|
if (cache) return cache;
|
||
|
|
const text = fs.readFileSync(VERSIONS_PATH, "utf8");
|
||
|
|
const out: Record<string, string> = {};
|
||
|
|
for (const raw of text.split("\n")) {
|
||
|
|
const line = raw.trim();
|
||
|
|
if (line === "" || line.startsWith("#")) continue;
|
||
|
|
const eq = line.indexOf("=");
|
||
|
|
if (eq < 0) continue;
|
||
|
|
out[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
|
||
|
|
}
|
||
|
|
cache = out;
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Resolve a container image by its VERSIONS key suffix, e.g. image("FORGEJO"). */
|
||
|
|
export function image(name: string): string {
|
||
|
|
const key = `IMAGE_${name.toUpperCase()}`;
|
||
|
|
const v = load()[key];
|
||
|
|
if (!v) {
|
||
|
|
throw new Error(`VERSIONS: ${key} is not defined (foundation/VERSIONS)`);
|
||
|
|
}
|
||
|
|
if (v.includes("PIN_DIGEST")) {
|
||
|
|
if (process.env.FOUNDATION_ALLOW_UNPINNED === "1") {
|
||
|
|
// validation only: drop the unresolved @sha256:PIN_DIGEST, use the tag
|
||
|
|
return v.replace(/@sha256:PIN_DIGEST$/, "");
|
||
|
|
}
|
||
|
|
throw new Error(
|
||
|
|
`VERSIONS: ${key} is not digest-pinned ('${v}'). Run the pin-digests ` +
|
||
|
|
`procedure (see VERSIONS header) before a real deploy, or set ` +
|
||
|
|
`FOUNDATION_ALLOW_UNPINNED=1 for local validation.`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return v;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Minimum version string for a host tool, e.g. toolMin("VAULT"). */
|
||
|
|
export function toolMin(name: string): string | undefined {
|
||
|
|
return load()[`TOOL_${name.toUpperCase()}_MIN`];
|
||
|
|
}
|