test: add deploy json executor drift preflight
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import {
|
||||
compareDeployJsonExecutorMirrors,
|
||||
deployJsonDriftResult,
|
||||
deployJsonSourceOfTruth,
|
||||
encodeDeployJsonServiceContract,
|
||||
k3sManifestExecutorMirror,
|
||||
parseDeployJsonServiceContract,
|
||||
type DeployJsonServiceContract,
|
||||
type DeployJsonExecutorMirror,
|
||||
} from "./src/deploy-json-contract";
|
||||
import { rootPath } from "./src/config";
|
||||
import { runArtifactRegistryCommand } from "./src/artifact-registry";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
||||
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
||||
}
|
||||
|
||||
function asRecord(value: unknown, label: string): JsonRecord {
|
||||
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, value);
|
||||
return value as JsonRecord;
|
||||
}
|
||||
|
||||
function asArray(value: unknown, label: string): unknown[] {
|
||||
assertCondition(Array.isArray(value), `${label} must be an array`, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
function deployService(environment: "dev" | "prod", serviceId: string): DeployJsonServiceContract {
|
||||
const deploy = asRecord(JSON.parse(readFileSync(rootPath("deploy.json"), "utf8")) as unknown, "deploy.json");
|
||||
const environments = asRecord(deploy.environments, "deploy.json.environments");
|
||||
const env = asRecord(environments[environment], `deploy.json.environments.${environment}`);
|
||||
const services = asArray(env.services, `deploy.json.environments.${environment}.services`);
|
||||
const raw = services.find((item) => asRecord(item, `${environment} service`).id === serviceId);
|
||||
assertCondition(raw !== undefined, `deploy.json must contain ${environment}/${serviceId}`);
|
||||
return parseDeployJsonServiceContract(raw, `deploy.json.environments.${environment}.services.${serviceId}`);
|
||||
}
|
||||
|
||||
const service = deployService("dev", "mdtodo");
|
||||
const artifact = asRecord(service.artifact, "mdtodo artifact");
|
||||
const consumer = asRecord(service.consumer, "mdtodo consumer");
|
||||
const targetContract = asRecord(consumer.target, "mdtodo consumer target");
|
||||
const runtime = asRecord(service.runtime, "mdtodo runtime");
|
||||
const memory = asRecord(runtime.memory, "mdtodo runtime memory");
|
||||
const commit = String(service.commitId);
|
||||
const sourceOfTruth = deployJsonSourceOfTruth(service, "dev");
|
||||
const deploySource = readFileSync(rootPath("scripts/src/deploy.ts"), "utf8");
|
||||
assertCondition(deploySource.includes('"--deploy-json-service", encodeDeployJsonServiceContract(service)'), "deploy executor must pass deploy.json service contract into artifact-registry", {});
|
||||
|
||||
const applyPlan = asRecord(await runArtifactRegistryCommand([
|
||||
"deploy-service",
|
||||
"--env",
|
||||
"dev",
|
||||
"--service",
|
||||
"mdtodo",
|
||||
"--commit",
|
||||
commit,
|
||||
"--source-repo",
|
||||
service.repo,
|
||||
"--deploy-ref",
|
||||
"origin/master:deploy.json#environments.dev.services.mdtodo",
|
||||
"--deploy-json-service",
|
||||
encodeDeployJsonServiceContract(service),
|
||||
"--dry-run",
|
||||
]), "artifact-registry dry-run result");
|
||||
const applyTarget = asRecord(applyPlan.target, "artifact-registry target");
|
||||
const applyRegistry = asRecord(applyPlan.registry, "artifact-registry registry");
|
||||
const applyRuntime = asRecord(applyPlan.runtime, "artifact-registry runtime");
|
||||
const applyRuntimeMemory = asRecord(applyRuntime.memory, "artifact-registry runtime memory");
|
||||
const applyDriftCheck = asRecord(applyPlan.driftCheck, "artifact-registry driftCheck");
|
||||
assertCondition(applyPlan.sourceOfTruth !== undefined, "artifact-registry dry-run should expose deploy.json sourceOfTruth", applyPlan);
|
||||
assertCondition(JSON.stringify(applyPlan.sourceOfTruth) === JSON.stringify(sourceOfTruth), "artifact-registry sourceOfTruth must enumerate deploy.json fields", applyPlan.sourceOfTruth);
|
||||
assertCondition(applyDriftCheck.ok === true, "artifact-registry dry-run must report a passing drift preflight", applyDriftCheck);
|
||||
assertCondition(applyRegistry.repository === artifact.repository, "artifact-registry dry-run repository must come from deploy.json", applyRegistry);
|
||||
assertCondition(applyRegistry.tag === commit, "artifact-registry dry-run tag must be deploy.json commitId", applyRegistry);
|
||||
assertCondition(applyRegistry.imageRef === `127.0.0.1:5000/${artifact.repository}:${commit}`, "artifact-registry imageRef must be deploy.json artifact repository + commit", applyRegistry);
|
||||
assertCondition(applyTarget.namespace === targetContract.namespace, "artifact-registry dry-run namespace must come from deploy.json", applyTarget);
|
||||
assertCondition(applyTarget.deployment === targetContract.deployment, "artifact-registry dry-run deployment must come from deploy.json", applyTarget);
|
||||
assertCondition(applyTarget.service === targetContract.service, "artifact-registry dry-run service must come from deploy.json", applyTarget);
|
||||
assertCondition(asArray(applyTarget.deployments, "artifact-registry deployments").some((item) => asRecord(item, "deployment").name === targetContract.deployment), "artifact-registry deployments must come from deploy.json target", applyTarget);
|
||||
assertCondition(applyTarget.stableImage === targetContract.stableImage, "artifact-registry stableImage must come from deploy.json", applyTarget);
|
||||
assertCondition(applyTarget.runtimeImage === `unidesk-mdtodo:${commit}`, "artifact-registry runtimeImage must derive from deploy.json stableImage + commit", applyTarget);
|
||||
assertCondition(applyTarget.manifestRepoPath === targetContract.manifestRepoPath, "artifact-registry manifest path must come from deploy.json", applyTarget);
|
||||
assertCondition(applyRuntime.sourceOfTruth === "deploy.json", "artifact-registry runtime source must be deploy.json", applyRuntime);
|
||||
assertCondition(applyRuntime.containerPort === runtime.containerPort, "artifact-registry runtime port must come from deploy.json", applyRuntime);
|
||||
assertCondition(applyRuntime.healthPath === runtime.healthPath, "artifact-registry runtime healthPath must come from deploy.json", applyRuntime);
|
||||
assertCondition(applyRuntimeMemory.request === memory.request && applyRuntimeMemory.limit === memory.limit, "artifact-registry memory must come from deploy.json", applyRuntimeMemory);
|
||||
|
||||
const manifestMirror = k3sManifestExecutorMirror(service);
|
||||
assertCondition(manifestMirror !== null, "mdtodo deploy.json contract should locate a k8s manifest mirror");
|
||||
const cleanDrifts = compareDeployJsonExecutorMirrors(service, "dev", [manifestMirror!]);
|
||||
assertCondition(cleanDrifts.length === 0, "current k8s manifest mirror should match deploy.json contract", cleanDrifts);
|
||||
|
||||
const driftMirror: DeployJsonExecutorMirror = {
|
||||
...manifestMirror!,
|
||||
runtime: {
|
||||
...manifestMirror!.runtime,
|
||||
containerPort: 4268,
|
||||
memory: {
|
||||
...manifestMirror!.runtime?.memory,
|
||||
limit: "384Mi",
|
||||
},
|
||||
},
|
||||
};
|
||||
const drift = compareDeployJsonExecutorMirrors(service, "dev", [driftMirror]);
|
||||
const driftResult = deployJsonDriftResult(service, "dev", drift);
|
||||
const driftPayload = asRecord(driftResult.drift, "drift result payload");
|
||||
const driftItems = asArray(driftPayload.items, "drift result items").map((item) => asRecord(item, "drift item"));
|
||||
assertCondition(driftResult.ok === false, "drift result must be non-ok", driftResult);
|
||||
assertCondition(driftResult.error === "deploy-json-drift", "drift result should use structured deploy-json-drift error", driftResult);
|
||||
assertCondition(driftItems.some((item) => item.field === "runtime.containerPort" && item.expected === 4267 && item.actual === 4268), "drift result should report port mismatch", driftItems);
|
||||
assertCondition(driftItems.some((item) => item.field === "runtime.memory.limit" && item.expected === "512Mi" && item.actual === "384Mi"), "drift result should report memory mismatch", driftItems);
|
||||
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
ok: true,
|
||||
checks: [
|
||||
"deploy executor passes the deploy.json service contract to artifact-registry dry-run",
|
||||
"artifact-registry dry-run reads mdtodo image, target, port and memory fields from deploy.json",
|
||||
"k8s manifest target/port/memory are treated as derived mirrors and checked for drift",
|
||||
"drift preflight returns structured deploy-json-drift with field-level expected/actual values",
|
||||
],
|
||||
serviceId: service.id,
|
||||
commit,
|
||||
sourceOfTruth,
|
||||
}, null, 2)}\n`);
|
||||
Reference in New Issue
Block a user