cast/src/bindings.ts

33 lines
997 B
TypeScript
Raw Normal View History

feat: cast — the Coolify executor, extracted from the infra state repo Public tool, private state. cast holds no hostnames, no bindings, no secrets: it joins a product repo's .infra/ manifest with a state directory you point it at, and makes Coolify match. Extracted from heavy-duty/infra, which was half tool and half state — the inconsistency that made it impossible to say whether "infra" named a CLI or a runbook. rig builds the boxes; cast fills them; infra is what they are filled with. Two changes were required to make it genuinely stateless and publishable: - The implicit cwd contract (environments.yaml / secrets/ / .coolify.env resolved against the working directory, silently reading the wrong file from the wrong place) is now an explicit --state <dir> / $CAST_STATE. - BANNED_IN_PROD — a hardcoded list of one product's ALLOW_* flags, the only product knowledge in the executor — becomes the generic, operator- owned environments.<env>.forbidden_var_patterns. The guard now lives in private state, so a product-side change cannot lower its own guard, and it is a pattern rather than a list, so it catches unforeseen siblings. Age identities resolve as $CAST_AGE_KEY_FILE_<ENV> then ~/.config/cast/age-<env>.key — which is the entire attended-vs-unattended apply mechanism, with no environment names known to the tool. Instance identity (org names, the GitHub App name, founder domains) is out of the fixtures and out of register-github-app.sh, which took APP_NAME and ORG as arguments rather than baking them in. 69 tests green; bin/cast + curl installer mirror rig's shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 12:25:44 +00:00
import { readFileSync } from "node:fs";
import { parse } from "yaml";
import { z } from "zod";
const BindingsSchema = z
.object({
environments: z.record(
z
.object({
server: z.string(),
s3_destination: z.string().optional(),
// Var-name patterns this environment refuses outright (see
// assertEnvVarPolicy). Operator-owned guard: prod typically bans
// whatever family of flags enables destructive tooling.
forbidden_var_patterns: z.array(z.string()).optional(),
})
.strict(),
),
github_apps: z.record(z.string()),
smoke_target: z.string().optional(),
})
.strict();
export type Bindings = z.infer<typeof BindingsSchema>;
export function loadBindings(path: string): Bindings {
const result = BindingsSchema.safeParse(parse(readFileSync(path, "utf8")));
if (!result.success) {
throw new Error(`invalid bindings ${path}: ${result.error.message}`);
}
return result.data;
}