Add decision-center k3s artifact consumer paths

This commit is contained in:
Codex
2026-05-19 16:22:03 +00:00
parent 2a21f6cda3
commit b2f16f235b
13 changed files with 543 additions and 161 deletions
+143 -25
View File
@@ -79,6 +79,7 @@ interface ServiceRuntimeState {
deploymentMode: string;
currentCommit: string | null;
healthCommit: string | null;
healthRequestedCommit: string | null;
imageCommit: string | null;
orchestratorCommit: string | null;
healthOk: boolean;
@@ -135,7 +136,7 @@ const nativeK3sCtrAddress = "/run/k3s/containerd/containerd.sock";
const unideskRepoUrl = "https://github.com/pikasTech/unidesk";
const d601MaintenanceDeployAllowedServiceIds = new Set<string>(["backend-core", "k3sctl-adapter", "code-queue"]);
const devApplySupportedServiceIds = new Set<string>(["backend-core"]);
const devArtifactConsumerServiceIds = new Set<string>(["baidu-netdisk", "frontend"]);
const devArtifactConsumerServiceIds = new Set<string>(["baidu-netdisk", "decision-center", "frontend"]);
const prodArtifactConsumerServiceIds = new Set<string>(["backend-core", "baidu-netdisk", "decision-center", "frontend"]);
const deployEnvironmentTargets: Record<DeployEnvironment, DeployEnvironmentTarget> = {
dev: {
@@ -199,7 +200,7 @@ export function deployHelp(action: string | undefined = undefined): Record<strin
},
options: [
{ name: "--file <path>", default: defaultDeployFile, description: "Desired-state manifest path relative to the repo root. JSON and ESM JS manifests are supported, for example deploy.json or develop.js. Local manifest apply allows k3sctl-adapter and explicit production code-queue controlled rollout on D601." },
{ name: "--env <dev|prod>", description: "Read the named environment from origin/master:deploy.json. Dev apply supports backend-core target-side rollout plus frontend/baidu-netdisk artifact consumers; prod apply uses the D601 registry artifact consumer for backend-core, frontend, baidu-netdisk, and decision-center." },
{ name: "--env <dev|prod>", description: "Read the named environment from origin/master:deploy.json. Dev apply supports backend-core target-side rollout plus reviewed artifact consumers for frontend, baidu-netdisk, and decision-center; prod apply uses the D601 registry artifact consumer for backend-core, frontend, baidu-netdisk, and decision-center." },
{ name: "--service <id>", description: "Limit reconcile to one service from the manifest." },
{ name: "--commit <full-sha>", description: "Prod artifact rollback/apply override for a selected service; the image must already exist in D601 registry." },
{ name: "--dry-run", description: "Prepare and validate without mutating the target service." },
@@ -662,6 +663,20 @@ function devK3sDeployService(id: string): UniDeskMicroserviceConfig | undefined
allowedMethods: ["GET", "HEAD"],
allowedPathPrefixes: ["/"],
},
"decision-center": {
name: "UniDesk Dev Decision Center",
description: "Isolated dev Decision Center deployed into D601 native k3s namespace unidesk-dev.",
dockerfile: "src/components/microservices/decision-center/Dockerfile",
composeFile: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-decision-center.k8s.yaml",
composeService: "decision-center-dev",
containerName: "k3s:decision-center-dev",
nodeBaseUrl: "k3s://decision-center-dev",
nodePort: 4277,
healthPath: "/health",
route: "/dev/decision-center",
allowedMethods: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"],
allowedPathPrefixes: ["/", "/api/", "/logs"],
},
"code-queue": {
name: "UniDesk Dev Code Queue",
description: "Isolated dev Code Queue execution plane deployed into D601 native k3s namespace unidesk-dev.",
@@ -738,7 +753,12 @@ function isCoreDeployService(service: UniDeskMicroserviceConfig): boolean {
function isDevK3sDeployService(service: UniDeskMicroserviceConfig): boolean {
return service.deployment.mode === "k3sctl-managed"
&& devApplySupportedServiceIds.has(service.id);
&& (devApplySupportedServiceIds.has(service.id) || devArtifactConsumerServiceIds.has(service.id));
}
function isDevArtifactConsumerService(service: UniDeskMicroserviceConfig): boolean {
return service.deployment.mode === "k3sctl-managed"
&& devArtifactConsumerServiceIds.has(service.id);
}
function isD601MaintenanceDeployBlocked(service: UniDeskMicroserviceConfig): boolean {
@@ -1822,6 +1842,12 @@ function healthDeployCommit(body: Record<string, unknown> | null): string | null
return commit.length > 0 ? commit : null;
}
function healthDeployRequestedCommit(body: Record<string, unknown> | null): string | null {
const deploy = asRecord(body?.deploy);
const commit = asString(deploy?.requestedCommit).toLowerCase();
return commit.length > 0 ? commit : null;
}
function healthSummary(response: unknown): Record<string, unknown> {
const record = asRecord(response);
const body = asRecord(record?.body);
@@ -1943,23 +1969,29 @@ function commitMatches(actual: string | null, desired: string): boolean {
function runtimeCommitVerified(
service: UniDeskMicroserviceConfig,
healthCommit: string | null,
healthRequestedCommit: string | null,
imageCommit: string | null,
orchestratorCommit: string | null,
desired: string,
): boolean {
if (service.deployment.mode === "k3sctl-managed") return commitMatches(orchestratorCommit, desired);
if (service.deployment.mode === "k3sctl-managed") {
if (healthRequestedCommit !== null && healthRequestedCommit.length > 0 && !commitMatches(healthRequestedCommit, desired)) return false;
return commitMatches(orchestratorCommit, desired);
}
if (healthCommit !== null && healthCommit.length > 0 && !commitMatches(healthCommit, desired)) return false;
if (healthRequestedCommit !== null && healthRequestedCommit.length > 0 && !commitMatches(healthRequestedCommit, desired)) return false;
return commitMatches(imageCommit, desired);
}
function runtimeCurrentCommit(
service: UniDeskMicroserviceConfig,
healthCommit: string | null,
healthRequestedCommit: string | null,
imageCommit: string | null,
orchestratorCommit: string | null,
): string | null {
if (service.deployment.mode === "k3sctl-managed") return orchestratorCommit ?? imageCommit;
return healthCommit ?? imageCommit ?? orchestratorCommit;
if (service.deployment.mode === "k3sctl-managed") return orchestratorCommit ?? healthCommit ?? healthRequestedCommit ?? imageCommit;
return healthCommit ?? healthRequestedCommit ?? imageCommit ?? orchestratorCommit;
}
function coreBody(response: unknown): Record<string, unknown> | null {
@@ -2195,18 +2227,37 @@ async function readK8sCommit(config: UniDeskConfig, service: UniDeskMicroservice
return commit.length > 0 ? commit : null;
}
function manifestEnvironmentForService(service: UniDeskMicroserviceConfig): DeployEnvironment {
return service.id === "decision-center" && service.deployment.namespace === "unidesk-dev" ? "dev" : "prod";
}
function serviceDeployRef(service: UniDeskMicroserviceConfig): string {
return `deploy.json#environments.${manifestEnvironmentForService(service)}.services.${service.id}`;
}
function k3sDeploymentManifestPath(service: UniDeskMicroserviceConfig): string {
if (service.id === "decision-center" && service.deployment.namespace === "unidesk-dev") {
return "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-decision-center.k8s.yaml";
}
if (service.id === "decision-center") {
return "src/components/microservices/k3sctl-adapter/k3s/decision-center.k8s.yaml";
}
return k8sManifestPath(service);
}
async function readRuntimeState(config: UniDeskConfig, service: UniDeskMicroserviceConfig, desired: DeployManifestService): Promise<ServiceRuntimeState> {
const reason = unsupportedReason(service);
const health = await serviceHealth(config, service);
const healthBody = coreBody(health);
const healthCommit = healthDeployCommit(healthBody);
const healthRequestedCommit = healthDeployRequestedCommit(healthBody);
const healthRecord = asRecord(health);
const healthOk = healthRecord?.ok === true && healthBody?.ok !== false;
const [imageCommit, orchestratorCommit] = await Promise.all([
readDockerImageCommit(config, service).catch(() => null),
readK8sCommit(config, service).catch(() => null),
]);
const currentCommit = runtimeCurrentCommit(service, healthCommit, imageCommit, orchestratorCommit);
const currentCommit = runtimeCurrentCommit(service, healthCommit, healthRequestedCommit, imageCommit, orchestratorCommit);
return {
serviceId: service.id,
ok: reason === null,
@@ -2218,10 +2269,11 @@ async function readRuntimeState(config: UniDeskConfig, service: UniDeskMicroserv
deploymentMode: service.deployment.mode,
currentCommit,
healthCommit,
healthRequestedCommit,
imageCommit,
orchestratorCommit,
healthOk,
upToDate: reason === null && healthOk && runtimeCommitVerified(service, healthCommit, imageCommit, orchestratorCommit, desired.commitId),
upToDate: reason === null && healthOk && runtimeCommitVerified(service, healthCommit, healthRequestedCommit, imageCommit, orchestratorCommit, desired.commitId),
raw: { health: healthSummary(health) },
};
}
@@ -2233,9 +2285,9 @@ async function healthVerify(config: UniDeskConfig, service: UniDeskMicroserviceC
let latest: ServiceRuntimeState | null = null;
while (Date.now() < deadline) {
latest = await readRuntimeState(config, service, { ...desired, commitId: resolvedCommit });
const commitOk = runtimeCommitVerified(service, latest.healthCommit, latest.imageCommit, latest.orchestratorCommit, resolvedCommit);
const commitOk = runtimeCommitVerified(service, latest.healthCommit, latest.healthRequestedCommit, latest.imageCommit, latest.orchestratorCommit, resolvedCommit);
const ok = latest.healthOk && commitOk;
progressLine("live-health", "probe", { serviceId: service.id, ok, healthOk: latest.healthOk, expectedCommit: resolvedCommit, healthCommit: latest.healthCommit, imageCommit: latest.imageCommit, orchestratorCommit: latest.orchestratorCommit });
progressLine("live-health", "probe", { serviceId: service.id, ok, healthOk: latest.healthOk, expectedCommit: resolvedCommit, healthCommit: latest.healthCommit, healthRequestedCommit: latest.healthRequestedCommit, imageCommit: latest.imageCommit, orchestratorCommit: latest.orchestratorCommit });
if (ok) {
return {
step: "live-health",
@@ -2297,23 +2349,75 @@ async function ensureGithubSshIdentityStep(config: UniDeskConfig, service: UniDe
}
}
async function runDevArtifactConsumerService(
service: UniDeskMicroserviceConfig,
desired: DeployManifestService,
options: DeployOptions,
before: ServiceRuntimeState,
): Promise<Record<string, unknown>> {
const commit = options.commitOverride ?? desired.commitId;
const artifactArgs = [
"deploy-service",
"--env", "dev",
"--service", service.id,
"--commit", commit,
"--source-repo", desired.repo,
"--timeout-ms", String(options.timeoutMs),
"--run-now",
...(options.dryRun ? ["--dry-run"] : []),
];
progressLine("dev-artifact-cd", options.dryRun ? "dry-run" : "reconcile", { serviceId: service.id, commit });
const result = await runArtifactRegistryCommand(artifactArgs);
const ok = asRecord(result)?.ok === true;
return {
ok,
action: ok ? "deployed" : "failed",
environment: "dev",
executor: "d601-registry-artifact-consumer",
resolvedCommit: commit,
before,
after: result,
steps: [{
step: "artifact-registry-consumer",
ok,
detail: JSON.stringify(result),
startedAt: nowIso(),
finishedAt: nowIso(),
raw: result,
}],
};
}
async function applyOneService(config: UniDeskConfig, service: UniDeskMicroserviceConfig, desired: DeployManifestService, options: DeployOptions): Promise<Record<string, unknown>> {
const steps: StepResult[] = [];
const startedAt = nowIso();
const reason = unsupportedReason(service);
if (reason !== null) return { ok: false, serviceId: service.id, skipped: true, reason, steps };
const before = await readRuntimeState(config, service, desired);
if (!options.force && before.upToDate) return { ok: true, serviceId: service.id, action: "noop", before, steps };
if (manifestEnvironmentForService(service) === "dev" && isDevArtifactConsumerService(service)) {
const artifact = await runDevArtifactConsumerService(service, desired, options, before);
return {
...artifact,
before,
serviceId: service.id,
startedAt,
finishedAt: nowIso(),
};
}
if (options.dryRun) return { ok: true, serviceId: service.id, action: "would-deploy", before, steps };
if (!options.dryRun && isD601MaintenanceDeployBlocked(service)) {
return {
ok: false,
serviceId: service.id,
skipped: true,
reason: `D601 target-side deployment is allowed only for k3sctl-adapter and dev backend-core; ${service.id} is not enabled. Use ci run-dev-e2e for smoke verification.`,
reason: `D601 target-side deployment is allowed only for k3sctl-adapter and dev backend-core; artifact consumers must use registry CD. ${service.id} is not enabled. Use ci run-dev-e2e for smoke verification.`,
steps,
};
}
const reason = unsupportedReason(service);
if (reason !== null) return { ok: false, serviceId: service.id, skipped: true, reason, steps };
const before = await readRuntimeState(config, service, desired);
if (!options.force && before.upToDate) return { ok: true, serviceId: service.id, action: "noop", before, steps };
if (options.dryRun) return { ok: true, serviceId: service.id, action: "would-deploy", before, steps };
const runId = `${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`;
const exportDir = targetExportDir(service, runId);
@@ -2438,6 +2542,8 @@ function environmentDryRunPlan(
commitId: options.commitOverride !== null && service.id === options.serviceId ? options.commitOverride : service.commitId,
}));
const fingerprint = databaseFingerprint(target);
const devUnsupported = devUnsupportedServices(manifest, options.serviceId);
const prodArtifactUnsupported = prodArtifactUnsupportedServices(manifest, options.serviceId);
return {
ok: environment === "prod"
? services.every((service) => prodArtifactConsumerServiceIds.has(service.id))
@@ -2480,7 +2586,7 @@ function environmentDryRunPlan(
? "d601-registry-artifact-consumer"
: "unsupported"
: devArtifactConsumerServiceIds.has(service.id)
? service.id === "frontend" ? "d601-registry-artifact-consumer" : "d601-registry-artifact-consumer-dev-validation"
? "d601-registry-artifact-consumer"
: devApplySupportedServiceIds.has(service.id)
? "d601-dev-target-side-build"
: "unsupported",
@@ -2493,8 +2599,15 @@ function environmentDryRunPlan(
? "No standardized prod D601 registry artifact consumer is implemented for this service; legacy maintenance-channel deployment is not allowed."
: "No standardized dev deployment or artifact validation path is implemented for this service.",
}
: environment === "dev" && !devApplySupportedServiceIds.has(service.id) && !devArtifactConsumerServiceIds.has(service.id)
? {
ok: false,
supported: false,
reason: "No standardized dev D601 registry artifact consumer is implemented for this service; legacy maintenance-channel deployment is not allowed.",
}
: undefined,
})),
unsupported: environment === "prod" ? prodArtifactUnsupported : devUnsupported,
};
}
@@ -2512,7 +2625,7 @@ function blockedD601MaintenanceDeployServices(config: UniDeskConfig, manifest: D
}
function d601MaintenanceDeployBlockMessage(blocked: string[]): string {
return `D601 target-side deployment is enabled only for k3sctl-adapter and dev backend-core; blocked services: ${blocked.join(", ")}. Use ci run-dev-e2e for dev smoke verification.`;
return `D601 target-side deployment is enabled only for k3sctl-adapter and dev backend-core; artifact consumers must use registry CD. Blocked services: ${blocked.join(", ")}. Use ci run-dev-e2e for dev smoke verification.`;
}
function selectedDevArtifactServices(manifest: DeployManifest, serviceId: string | null): DeployManifestService[] {
@@ -2532,6 +2645,11 @@ function selectedEnvironmentServices(manifest: DeployManifest, serviceId: string
return services;
}
function devUnsupportedServices(manifest: DeployManifest, serviceId: string | null): DeployManifestService[] {
if (manifest.environment !== "dev") return [];
return selectedEnvironmentServices(manifest, serviceId).filter((service) => !devApplySupportedServiceIds.has(service.id) && !devArtifactConsumerServiceIds.has(service.id));
}
function prodArtifactUnsupportedServices(manifest: DeployManifest, serviceId: string | null): DeployManifestService[] {
return selectedEnvironmentServices(manifest, serviceId).filter((service) => !prodArtifactConsumerServiceIds.has(service.id));
}
@@ -2569,7 +2687,7 @@ async function runArtifactConsumerApplyNow(
"--commit", commit,
"--source-repo", service.repo,
"--deploy-ref", `${deployEnvironmentTargets[environment].gitRef}:deploy.json#environments.${environment}.services.${service.id}`,
...(environment === "prod" || service.id === "frontend" ? ["--env", environment] : []),
...(environment === "prod" || service.id === "frontend" || service.id === "decision-center" ? ["--env", environment] : []),
"--timeout-ms", String(options.timeoutMs),
"--run-now",
...(options.dryRun ? ["--dry-run"] : []),
@@ -2690,7 +2808,7 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
}
const unsupported = unsupportedDevApplyServices(manifest, options.serviceId);
if (unsupported.length > 0) {
throw new Error(`deploy apply --env dev currently supports backend-core target-side rollout plus frontend/baidu-netdisk artifact consumers; unsupported selected services: ${unsupported.join(", ")}. Use ci run-dev-e2e for smoke verification.`);
throw new Error(`deploy apply --env dev currently supports backend-core target-side rollout plus frontend/baidu-netdisk/decision-center artifact consumers; unsupported selected services: ${unsupported.join(", ")}. Use ci run-dev-e2e for smoke verification.`);
}
const devArtifactServices = selectedDevArtifactServices(manifest, options.serviceId);
const devTargetServices = selectedDevTargetServices(manifest, options.serviceId);
@@ -2703,11 +2821,11 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
}
if (config === null) throw new Error("deploy apply --env dev requires config.json");
if (!options.dryRun) {
const blocked = blockedD601MaintenanceDeployServices(config, manifest, options.serviceId);
const blocked = blockedD601MaintenanceDeployServices(config, manifest, options.serviceId).filter((serviceId) => serviceId !== "decision-center");
if (blocked.length > 0) throw new Error(d601MaintenanceDeployBlockMessage(blocked));
}
if (!options.runNow) return applyJob(config, args, options);
return await runApplyNow(config, manifest, options);
if (options.dryRun || options.runNow) return await runApplyNow(config, manifest, options);
return applyJob(config, args, options);
}
if (config === null) throw new Error("deploy local manifest mode requires config.json");
const manifest = resolveManifestCommits(await readDeployManifest(options.file), options.serviceId);
@@ -2716,8 +2834,8 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
const blocked = blockedD601MaintenanceDeployServices(config, manifest, options.serviceId);
if (blocked.length > 0) throw new Error(d601MaintenanceDeployBlockMessage(blocked));
}
if (!options.runNow) return applyJob(config, args, options);
return await runApplyNow(config, manifest, options);
if (options.dryRun || options.runNow) return await runApplyNow(config, manifest, options);
return applyJob(config, args, options);
}
export async function runCodeQueueDeployCompatCommand(_config: UniDeskConfig, args: string[]): Promise<unknown> {