Files
pikasTech-HWLAB/scripts/validate-dev-m3-cardinality.mjs
2026-06-08 12:35:31 +08:00

105 lines
4.4 KiB
JavaScript

#!/usr/bin/env node
import assert from "node:assert/strict";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { readStructuredFile } from "./src/structured-config.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const namespace = "hwlab-dev";
async function readConfig(relativePath) {
return readStructuredFile(repoRoot, relativePath);
}
function listItems(document) {
return document?.kind === "List" ? document.items ?? [] : [document];
}
function serviceIdsFromDeploy(deploy) {
return (deploy.services ?? []).map((service) => service.serviceId).filter(Boolean);
}
function assertUnique(label, values) {
assert.equal(new Set(values).size, values.length, `${label} must be unique`);
}
function assertDeployManifestConsistency(deploy) {
assert.equal(deploy.environment, "dev", "deploy/deploy.yaml environment");
assert.equal(deploy.namespace, namespace, "deploy/deploy.yaml namespace");
assert.equal(deploy.profiles?.prod?.enabled, false, "deploy/deploy.yaml prod disabled");
const serviceIds = serviceIdsFromDeploy(deploy);
assert.ok(serviceIds.length > 0, "deploy/deploy.yaml services must not be empty");
assertUnique("deploy/deploy.yaml serviceId", serviceIds);
const byId = new Set(serviceIds);
for (const mapping of deploy.k3s?.serviceMappings ?? []) {
assert.ok(byId.has(mapping.serviceId), `deploy/deploy.yaml k3s serviceMapping ${mapping.name} references a declared service`);
assert.equal(mapping.namespace, namespace, `${mapping.name} namespace`);
}
const v02 = deploy.lanes?.v02;
if (v02) {
const envReuseServices = Array.isArray(v02.envReuseServices) ? v02.envReuseServices.filter(Boolean) : [];
assert.ok(envReuseServices.length > 0, "deploy.lanes.v02.envReuseServices must not be empty");
assertUnique("deploy.lanes.v02.envReuseServices", envReuseServices);
for (const serviceId of envReuseServices) {
assert.ok(byId.has(serviceId), `deploy.lanes.v02.envReuseServices ${serviceId} must be declared in deploy.services`);
assert.ok(v02.serviceDeclarations?.[serviceId], `deploy.lanes.v02.serviceDeclarations.${serviceId} is required`);
assert.ok(v02.bootScripts?.[serviceId], `deploy.lanes.v02.bootScripts.${serviceId} is required`);
}
}
}
function assertK8sServiceMappings(deploy, services) {
const serviceDocs = listItems(services).filter((item) => item?.kind === "Service");
const serviceNames = new Set(serviceDocs.map((item) => item.metadata?.name).filter(Boolean));
for (const mapping of deploy.k3s?.serviceMappings ?? []) {
assert.ok(serviceNames.has(mapping.name), `deploy/k8s/base/services.yaml missing Service ${mapping.name}`);
const service = serviceDocs.find((item) => item.metadata?.name === mapping.name);
const port = service?.spec?.ports?.find((entry) => entry.name === "http") ?? service?.spec?.ports?.[0];
assert.equal(port?.port, mapping.port, `${mapping.name} service port`);
assert.equal(port?.targetPort, mapping.targetPort, `${mapping.name} targetPort`);
}
}
function assertRuntimeServiceWorkloads(deploy, workloads) {
const workloadNames = new Set(listItems(workloads).map((item) => item?.metadata?.name).filter(Boolean));
for (const service of deploy.services ?? []) {
if (service.externalImage || service.kind === "external") continue;
assert.ok(workloadNames.has(service.serviceId), `deploy/k8s/base/workloads.yaml missing workload ${service.serviceId}`);
}
}
export async function readDevM3CardinalityInputs() {
const [deploy, workloads, services, topology] = await Promise.all([
readConfig("deploy/deploy.yaml"),
readConfig("deploy/k8s/base/workloads.yaml"),
readConfig("deploy/k8s/base/services.yaml"),
readConfig("fixtures/mvp/m3-hardware-loop/topology.json").catch(() => null)
]);
return { deploy, workloads, services, topology };
}
export function validateDevM3Cardinality({ deploy, workloads, services }) {
assertDeployManifestConsistency(deploy);
assertK8sServiceMappings(deploy, services);
assertRuntimeServiceWorkloads(deploy, workloads);
}
export async function main() {
validateDevM3Cardinality(await readDevM3CardinalityInputs());
console.log("validated DEV deploy manifest service mapping consistency");
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
try {
await main();
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
}
}