test: add deploy json executor drift preflight
This commit is contained in:
+127
-19
@@ -9,15 +9,23 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
|
||||
import { startJob } from "./jobs";
|
||||
import { coreInternalFetch } from "./microservices";
|
||||
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "./code-queue-source-guard";
|
||||
import {
|
||||
compareDeployJsonExecutorMirrors,
|
||||
deployJsonCommitImage,
|
||||
deployJsonDriftResult,
|
||||
deployJsonSourceOfTruth,
|
||||
encodeDeployJsonServiceContract,
|
||||
hasDeployJsonExecutorContract,
|
||||
k3sManifestExecutorMirror,
|
||||
parseDeployJsonServiceContract,
|
||||
type DeployJsonExecutorMirror,
|
||||
type DeployJsonServiceContract,
|
||||
} from "./deploy-json-contract";
|
||||
|
||||
type DeployAction = "check" | "plan" | "apply";
|
||||
type DeployEnvironment = "dev" | "prod";
|
||||
|
||||
interface DeployManifestService {
|
||||
id: string;
|
||||
repo: string;
|
||||
commitId: string;
|
||||
}
|
||||
type DeployManifestService = DeployJsonServiceContract;
|
||||
|
||||
interface DeployManifest {
|
||||
schemaVersion: 1 | 2;
|
||||
@@ -512,17 +520,16 @@ function parseOptions(args: string[]): DeployOptions {
|
||||
}
|
||||
|
||||
function parseDeployManifestService(item: unknown, index: number, seen: Set<string>, path: string): DeployManifestService {
|
||||
const service = asRecord(item);
|
||||
if (service === null) throw new Error(`deploy manifest ${path}[${index}] must be an object`);
|
||||
const id = asString(service.id);
|
||||
const repo = asString(service.repo);
|
||||
const commitId = asString(service.commitId).toLowerCase();
|
||||
const service = parseDeployJsonServiceContract(item, `deploy manifest ${path}[${index}]`);
|
||||
const id = service.id;
|
||||
const repo = service.repo;
|
||||
const commitId = service.commitId;
|
||||
if (id.length === 0) throw new Error(`deploy manifest ${path}[${index}].id must be a non-empty string`);
|
||||
if (repo.length === 0) throw new Error(`deploy manifest ${path}[${index}].repo must be a non-empty string`);
|
||||
if (!/^[0-9a-f]{7,40}$/iu.test(commitId)) throw new Error(`deploy manifest ${path}[${index}].commitId must be a 7-40 char git SHA`);
|
||||
if (seen.has(id)) throw new Error(`duplicate deploy manifest service id: ${id}`);
|
||||
seen.add(id);
|
||||
return { id, repo, commitId };
|
||||
return service;
|
||||
}
|
||||
|
||||
function parseDeployManifestServices(value: unknown, path: string): DeployManifestService[] {
|
||||
@@ -896,6 +903,11 @@ function artifactConsumerPlanKind(service: UniDeskMicroserviceConfig, environmen
|
||||
return "unsupported";
|
||||
}
|
||||
|
||||
function artifactConsumerPlanKindFromDeployJson(service: DeployManifestService, fallback: ArtifactConsumerPlanKind): ArtifactConsumerPlanKind {
|
||||
if (service.consumer?.kind === "d601-k3s-managed") return "d601-k3s-managed";
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function deploymentPathForEnvironmentService(service: UniDeskMicroserviceConfig, environment: DeployEnvironment): string {
|
||||
return deploymentPathForEnvironmentServiceId(service.id, environment);
|
||||
}
|
||||
@@ -959,6 +971,35 @@ function artifactConsumerPlanTarget(service: UniDeskMicroserviceConfig, environm
|
||||
return common;
|
||||
}
|
||||
|
||||
function artifactConsumerPlanTargetFromDeployJson(
|
||||
service: DeployManifestService,
|
||||
fallbackService: UniDeskMicroserviceConfig,
|
||||
environment: DeployEnvironment,
|
||||
fallbackTarget: Record<string, unknown> | null,
|
||||
): Record<string, unknown> | null {
|
||||
if (service.consumer?.kind !== "d601-k3s-managed") return fallbackTarget;
|
||||
const target = service.consumer.target;
|
||||
const liveBlockReason = environment === "prod" ? prodArtifactLiveApplyBlockedServiceIds.get(service.id) ?? null : null;
|
||||
const dryRunBlockedReason = artifactConsumerDryRunBlockedServiceIds.get(service.id) ?? null;
|
||||
return {
|
||||
consumerKind: service.consumer.kind,
|
||||
runtimeHost: "D601",
|
||||
deploymentMode: fallbackService.deployment.mode,
|
||||
noRuntimeSourceBuild: service.consumer.noRuntimeSourceBuild,
|
||||
dryRunOnly: dryRunBlockedReason !== null || (environment === "prod" && prodArtifactLiveApplyBlockedServiceIds.has(service.id)),
|
||||
blockedReason: dryRunBlockedReason ?? liveBlockReason,
|
||||
namespace: target.namespace,
|
||||
deployment: target.deployment,
|
||||
service: target.service,
|
||||
containerName: target.containerName,
|
||||
manifest: target.manifestRepoPath,
|
||||
targetImage: target.stableImage,
|
||||
deployCommandShape: "registry manifest check + native k3s containerd import + Kubernetes Deployment image/env/annotation update + API service proxy health",
|
||||
forbiddenActions: ["docker build", "docker compose build", "NodePort", "hostPort", "provider-gateway direct business backend"],
|
||||
sourceOfTruth: deployJsonSourceOfTruth(service, environment),
|
||||
};
|
||||
}
|
||||
|
||||
function artifactConsumerTargetImageHint(service: UniDeskMicroserviceConfig, environment: DeployEnvironment): string {
|
||||
if (service.id === "frontend" && environment === "prod") return "unidesk-frontend";
|
||||
if (service.id === "frontend" && environment === "dev") return "unidesk-frontend:dev";
|
||||
@@ -970,6 +1011,44 @@ function artifactConsumerTargetImageHint(service: UniDeskMicroserviceConfig, env
|
||||
return container;
|
||||
}
|
||||
|
||||
function deployJsonExecutorMirrors(
|
||||
service: DeployManifestService,
|
||||
serviceConfig: UniDeskMicroserviceConfig,
|
||||
environment: DeployEnvironment,
|
||||
planKind: ArtifactConsumerPlanKind,
|
||||
planTarget: Record<string, unknown> | null,
|
||||
): DeployJsonExecutorMirror[] {
|
||||
const mirrors: DeployJsonExecutorMirror[] = [];
|
||||
mirrors.push({
|
||||
surface: "deploy-executor-plan",
|
||||
artifact: {
|
||||
kind: "source-build",
|
||||
repository: `unidesk/${service.id}`,
|
||||
tag: "commitId",
|
||||
},
|
||||
consumer: {
|
||||
kind: planKind === "d601-k3s-managed" ? "d601-k3s-managed" : planKind,
|
||||
noRuntimeSourceBuild: planKind !== "d601-dev-target-side-build",
|
||||
target: planTarget === null ? undefined : {
|
||||
namespace: typeof planTarget.namespace === "string" ? planTarget.namespace : undefined,
|
||||
deployment: typeof planTarget.deployment === "string" ? planTarget.deployment : undefined,
|
||||
service: typeof planTarget.service === "string" ? planTarget.service : undefined,
|
||||
containerName: typeof planTarget.containerName === "string" ? planTarget.containerName : undefined,
|
||||
stableImage: typeof planTarget.targetImage === "string" ? planTarget.targetImage : undefined,
|
||||
manifestRepoPath: typeof planTarget.manifest === "string" ? planTarget.manifest : undefined,
|
||||
},
|
||||
},
|
||||
runtime: {
|
||||
containerPort: serviceConfig.backend.nodePort,
|
||||
servicePort: serviceConfig.backend.nodePort,
|
||||
healthPath: serviceConfig.backend.healthPath,
|
||||
},
|
||||
});
|
||||
const manifestMirror = k3sManifestExecutorMirror(service);
|
||||
if (manifestMirror !== null) mirrors.push(manifestMirror);
|
||||
return mirrors;
|
||||
}
|
||||
|
||||
function artifactConsumerPlanValidation(service: UniDeskMicroserviceConfig, environment: DeployEnvironment): string[] {
|
||||
const kind = artifactConsumerPlanKind(service, environment);
|
||||
if (kind === "d601-dev-target-side-build") {
|
||||
@@ -2969,9 +3048,24 @@ function environmentDryRunPlan(
|
||||
const planTarget = serviceConfig === null ? null : artifactConsumerPlanTarget(serviceConfig, environment);
|
||||
const runtimeSecrets = serviceConfig === null ? undefined : redactedSecretContractForService(config, serviceConfig, environment);
|
||||
const unsupportedReason = unsupported ? unsupportedEnvironmentPlanReason(service.id, environment) : null;
|
||||
const deployJsonContract = hasDeployJsonExecutorContract(service);
|
||||
const effectivePlanKind = deployJsonContract
|
||||
? artifactConsumerPlanKindFromDeployJson(service, planKind)
|
||||
: planKind;
|
||||
const deployJsonPlanTarget = serviceConfig === null
|
||||
? null
|
||||
: artifactConsumerPlanTargetFromDeployJson(service, serviceConfig, environment, planTarget);
|
||||
const drifts = deployJsonContract && serviceConfig !== null && !unsupported
|
||||
? compareDeployJsonExecutorMirrors(service, environment, deployJsonExecutorMirrors(service, serviceConfig, environment, effectivePlanKind, deployJsonPlanTarget))
|
||||
: [];
|
||||
if (drifts.length > 0) return deployJsonDriftResult(service, environment, drifts);
|
||||
const effectiveTarget = unsupported
|
||||
? unsupportedPlanTarget(service.id, environment, unsupportedReason ?? "unsupported")
|
||||
: planTarget;
|
||||
: deployJsonContract
|
||||
? deployJsonPlanTarget
|
||||
: planTarget;
|
||||
const registryRepository = service.artifact?.repository ?? `unidesk/${service.id}`;
|
||||
const stableImage = service.consumer?.target.stableImage;
|
||||
return {
|
||||
id: service.id,
|
||||
repo: service.repo,
|
||||
@@ -2989,13 +3083,13 @@ function environmentDryRunPlan(
|
||||
dryRunOnly: true,
|
||||
blockedReason: "service is missing from config.json and no synthetic deploy service is available for this environment",
|
||||
} : {
|
||||
consumerKind: unsupported ? "unsupported" : planKind,
|
||||
registryImage: unsupported ? null : `127.0.0.1:5000/unidesk/${service.id}:${service.commitId}`,
|
||||
consumerKind: unsupported ? "unsupported" : effectivePlanKind,
|
||||
registryImage: unsupported ? null : `127.0.0.1:5000/${registryRepository}:${service.commitId}`,
|
||||
registry: unsupported ? null : {
|
||||
endpoint: "http://127.0.0.1:5000",
|
||||
repository: `unidesk/${service.id}`,
|
||||
repository: registryRepository,
|
||||
tag: service.commitId,
|
||||
imageRef: `127.0.0.1:5000/unidesk/${service.id}:${service.commitId}`,
|
||||
imageRef: `127.0.0.1:5000/${registryRepository}:${service.commitId}`,
|
||||
digest: null,
|
||||
digestHeader: "Docker-Content-Digest",
|
||||
digestSource: "planned registry manifest HEAD; live apply records Docker-Content-Digest before mutation",
|
||||
@@ -3006,9 +3100,9 @@ function environmentDryRunPlan(
|
||||
deployRef: `${source.ref}:deploy.json#environments.${environment}.services.${service.id}`,
|
||||
},
|
||||
build: unsupported ? null : {
|
||||
willCompile: planKind === "d601-dev-target-side-build",
|
||||
willCompile: effectivePlanKind === "d601-dev-target-side-build",
|
||||
willRunCargoBuild: false,
|
||||
willRunDockerBuild: planKind === "d601-dev-target-side-build",
|
||||
willRunDockerBuild: effectivePlanKind === "d601-dev-target-side-build",
|
||||
willRunDockerComposeBuild: false,
|
||||
producerBoundary: service.id === "backend-core" ? "ci publish-backend-core" : "ci publish-user-service",
|
||||
},
|
||||
@@ -3018,12 +3112,25 @@ function environmentDryRunPlan(
|
||||
"unidesk.ai/source-commit": service.commitId,
|
||||
"unidesk.ai/dockerfile": serviceConfig.repository.dockerfile,
|
||||
},
|
||||
noRuntimeSourceBuild: unsupported || planKind !== "d601-dev-target-side-build",
|
||||
noRuntimeSourceBuild: unsupported || (service.consumer?.noRuntimeSourceBuild ?? effectivePlanKind !== "d601-dev-target-side-build"),
|
||||
dryRunOnly: unsupported || (environment === "prod" && prodArtifactLiveApplyBlockedServiceIds.has(service.id)) || dryRunBlockedReason !== null,
|
||||
blockedReason: unsupportedReason ?? dryRunBlockedReason ?? (environment === "prod" ? prodArtifactLiveApplyBlockedServiceIds.get(service.id) ?? null : null),
|
||||
runtimeSecrets,
|
||||
sourceOfTruth: deployJsonContract ? deployJsonSourceOfTruth(service, environment) : undefined,
|
||||
driftCheck: deployJsonContract ? {
|
||||
ok: true,
|
||||
mirrors: ["deploy-executor-plan", ...(service.consumer?.kind === "d601-k3s-managed" ? ["k8s-manifest"] : [])],
|
||||
} : undefined,
|
||||
},
|
||||
target: effectiveTarget,
|
||||
runtime: service.runtime === undefined ? undefined : {
|
||||
sourceOfTruth: "deploy.json",
|
||||
containerPort: service.runtime.containerPort,
|
||||
healthPath: service.runtime.healthPath,
|
||||
memory: service.runtime.memory,
|
||||
health: service.runtime.health,
|
||||
},
|
||||
runtimeImage: stableImage === undefined ? undefined : deployJsonCommitImage(stableImage, service.commitId),
|
||||
validation: unsupported
|
||||
? ["unsupported service remains blocked before source materialization, registry pull, manifest update, rollout, or runtime mutation"]
|
||||
: serviceConfig === null
|
||||
@@ -3197,6 +3304,7 @@ async function runArtifactConsumerApplyNow(
|
||||
"--commit", commit,
|
||||
"--source-repo", service.repo,
|
||||
"--deploy-ref", `${deployEnvironmentTargets[environment].gitRef}:deploy.json#environments.${environment}.services.${service.id}`,
|
||||
...(options.dryRun && hasDeployJsonExecutorContract(service) ? ["--deploy-json-service", encodeDeployJsonServiceContract(service)] : []),
|
||||
"--env", environment,
|
||||
"--timeout-ms", String(options.timeoutMs),
|
||||
"--run-now",
|
||||
|
||||
Reference in New Issue
Block a user