// 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:`. // // 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 | undefined; function load(): Record { if (cache) return cache; const text = fs.readFileSync(VERSIONS_PATH, "utf8"); const out: Record = {}; 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`]; }