test: add deploy json executor drift preflight

This commit is contained in:
Codex
2026-05-21 13:25:16 +00:00
parent 70263014b5
commit fc87e680e4
7 changed files with 825 additions and 46 deletions
+103 -19
View File
@@ -904,6 +904,9 @@ export function parseArtifactRegistryOptions(args: string[]): ArtifactRegistryOp
} else if (arg === "--deploy-ref") {
options.deployRef = requireValue(args, index, arg);
index += 1;
} else if (arg === "--deploy-json-service") {
options.deployJsonService = parseDeployJsonServiceContractBase64(requireValue(args, index, arg));
index += 1;
} else if (arg === "--env" || arg === "--environment") {
options.environment = environmentValue(requireValue(args, index, arg), arg);
index += 1;
@@ -1365,7 +1368,7 @@ function artifactConsumerLiveBlock(spec: ArtifactConsumerSpec, options: Artifact
}
function artifactImageRef(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, commit: string): string {
return `127.0.0.1:${options.port}/${spec.registryRepository}:${commit}`;
return `127.0.0.1:${options.port}/${(options.dryRun ? options.deployJsonService?.artifact?.repository : undefined) ?? spec.registryRepository}:${commit}`;
}
function sourceRepoFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): string {
@@ -1374,7 +1377,68 @@ function sourceRepoFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerS
function deployRefFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): string {
const target = artifactConsumerTarget(spec, options.environment);
return options.deployRef ?? target?.deployRef ?? `deploy.json#environments.${options.environment ?? "prod"}.services.${spec.serviceId}`;
return options.deployRef ?? (options.dryRun ? options.deployJsonService?.consumer?.targetRef : undefined) ?? target?.deployRef ?? `deploy.json#environments.${options.environment ?? "prod"}.services.${spec.serviceId}`;
}
function deployJsonServiceForOptions(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, environment: ArtifactDeployEnvironment): DeployJsonServiceContract | null {
if (!options.dryRun) return null;
if (options.deployJsonService !== null) return options.deployJsonService;
if (environment === "dev" && spec.serviceId === "mdtodo") {
return readDeployJsonServiceContractFromFile(environment, spec.serviceId);
}
return null;
}
function targetImageFor(options: ArtifactRegistryOptions, target: ArtifactConsumerTarget): string {
return (options.dryRun ? options.deployJsonService?.consumer?.target.stableImage : undefined) ?? target.targetImage;
}
function targetCommitImageFor(options: ArtifactRegistryOptions, target: ArtifactConsumerTarget, commit: string): string {
const stableImage = options.dryRun ? options.deployJsonService?.consumer?.target.stableImage : undefined;
return stableImage === undefined ? target.targetCommitImage(commit) : deployJsonCommitImage(stableImage, commit);
}
function registryRepositoryFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): string {
return (options.dryRun ? options.deployJsonService?.artifact?.repository : undefined) ?? spec.registryRepository;
}
function artifactRegistryDeployJsonMirrors(
options: ArtifactRegistryOptions,
spec: ArtifactConsumerSpec,
target: ArtifactConsumerTarget,
): DeployJsonExecutorMirror[] {
const mirrors: DeployJsonExecutorMirror[] = [
{
surface: "artifact-registry-executor",
artifact: {
kind: "source-build",
repository: spec.registryRepository,
tag: "commitId",
},
consumer: {
kind: spec.kind === "d601-k3s" ? "d601-k3s-managed" : spec.kind,
noRuntimeSourceBuild: true,
target: target.k3s === undefined ? undefined : {
namespace: target.k3s.namespace,
deployment: target.k3s.deploymentName,
service: target.k3s.serviceName,
containerName: target.k3s.containerName,
stableImage: target.targetImage,
manifestRepoPath: target.k3s.manifestRepoPath,
},
},
runtime: target.k3s === undefined ? undefined : {
containerPort: target.k3s.servicePort,
servicePort: target.k3s.servicePort,
healthPath: target.k3s.healthPath,
},
},
];
if (options.deployJsonService !== null) {
const manifestMirror = k3sManifestExecutorMirror(options.deployJsonService);
if (manifestMirror !== null) mirrors.push(manifestMirror);
}
return mirrors;
}
function runRemoteScript(options: ArtifactRegistryOptions, script: string, timeoutMs = options.timeoutMs, runtime: ArtifactRegistryCommandRuntime = {}): CommandResult {
@@ -2469,16 +2533,24 @@ function legacyDeployBackendCoreResult(options: ArtifactRegistryOptions): Record
function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): Record<string, unknown> {
const environment = options.environment ?? "prod";
const deployJsonService = deployJsonServiceForOptions(options, spec, environment);
const effectiveOptions = deployJsonService === options.deployJsonService ? options : { ...options, deployJsonService };
const drifts = deployJsonService !== null && hasDeployJsonExecutorContract(deployJsonService)
? compareDeployJsonExecutorMirrors(deployJsonService, environment, artifactRegistryDeployJsonMirrors(effectiveOptions, spec, target))
: [];
if (drifts.length > 0) return deployJsonDriftResult(deployJsonService, environment, drifts);
const verificationBlocked = spec.runtimeVerification === "blocked";
const livePolicy = environment === "prod" ? spec.prodLiveApply : "enabled";
const sourceImage = artifactImageRef(options, spec, commit);
const registryEndpoint = `http://127.0.0.1:${options.port}`;
const sourceImage = artifactImageRef(effectiveOptions, spec, commit);
const registryEndpoint = `http://127.0.0.1:${effectiveOptions.port}`;
const config = readConfig();
const contractTarget = deployJsonService?.consumer?.target;
const contractRuntime = deployJsonService?.runtime;
const k3sDeployments = target.k3s === undefined
? []
: [
{ name: target.k3s.deploymentName, containerName: target.k3s.containerName },
...(target.k3s.extraDeployments ?? []).map((deployment) => typeof deployment === "string"
{ name: contractTarget?.deployment ?? target.k3s.deploymentName, containerName: contractTarget?.containerName ?? target.k3s.containerName },
...(contractTarget === undefined ? (target.k3s.extraDeployments ?? []) : []).map((deployment) => typeof deployment === "string"
? { name: deployment, containerName: target.k3s!.containerName }
: { name: deployment.name, containerName: deployment.containerName ?? target.k3s!.containerName }),
];
@@ -2489,20 +2561,20 @@ function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: Arti
mutation: false,
error: verificationBlocked ? "runtime-verification-blocked" : undefined,
environment,
providerId: options.providerId,
providerId: effectiveOptions.providerId,
serviceId: spec.serviceId,
commit,
sourceRepo: sourceRepoFor(options, spec),
deployRef: deployRefFor(options, spec),
sourceRepo: sourceRepoFor(effectiveOptions, spec),
deployRef: deployRefFor(effectiveOptions, spec),
sourceImage,
source: {
repo: sourceRepoFor(options, spec),
repo: sourceRepoFor(effectiveOptions, spec),
commit,
dockerfile: spec.dockerfile,
},
registry: {
endpoint: registryEndpoint,
repository: spec.registryRepository,
repository: registryRepositoryFor(effectiveOptions, spec),
tag: commit,
imageRef: sourceImage,
digest: null,
@@ -2518,13 +2590,13 @@ function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: Arti
},
requiredLabels: {
"unidesk.ai/service-id": spec.serviceId,
"unidesk.ai/source-repo": sourceRepoFor(options, spec),
"unidesk.ai/source-repo": sourceRepoFor(effectiveOptions, spec),
"unidesk.ai/source-commit": commit,
"unidesk.ai/dockerfile": spec.dockerfile,
},
registryProbe: {
method: "HEAD",
url: `${registryEndpoint}/v2/${spec.registryRepository}/manifests/${commit}`,
url: `${registryEndpoint}/v2/${registryRepositoryFor(effectiveOptions, spec)}/manifests/${commit}`,
digestHeader: "Docker-Content-Digest",
},
boundary: `${environment} CD is artifact-consumer only: verify commit-pinned registry image, pull/import, deploy, then verify live commit/image/health; it never builds source on the runtime target`,
@@ -2533,6 +2605,11 @@ function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: Arti
allowed: !verificationBlocked && (environment !== "prod" || spec.prodLiveApply === "enabled"),
reason: spec.runtimeVerificationBlockReason ?? (environment === "prod" ? spec.prodLiveBlockReason ?? null : null),
},
sourceOfTruth: deployJsonService !== null && hasDeployJsonExecutorContract(deployJsonService) ? deployJsonSourceOfTruth(deployJsonService, environment) : undefined,
driftCheck: deployJsonService !== null && hasDeployJsonExecutorContract(deployJsonService) ? {
ok: true,
mirrors: ["artifact-registry-executor", ...(deployJsonService.consumer?.kind === "d601-k3s-managed" ? ["k8s-manifest"] : [])],
} : undefined,
};
if (spec.kind === "compose" || spec.kind === "d601-compose") {
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
@@ -2598,15 +2675,22 @@ function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: Arti
...common,
target: {
kind: "d601-k3s",
namespace: target.k3s?.namespace,
deployment: target.k3s?.deploymentName,
namespace: contractTarget?.namespace ?? target.k3s?.namespace,
deployment: contractTarget?.deployment ?? target.k3s?.deploymentName,
deployments: k3sDeployments,
service: target.k3s?.serviceName,
stableImage: target.targetImage,
runtimeImage: target.targetCommitImage(commit),
manifestRepoPath: target.k3s?.manifestRepoPath,
service: contractTarget?.service ?? target.k3s?.serviceName,
stableImage: targetImageFor(effectiveOptions, target),
runtimeImage: targetCommitImageFor(effectiveOptions, target, commit),
manifestRepoPath: contractTarget?.manifestRepoPath ?? target.k3s?.manifestRepoPath,
deployCommandShape: "kubectl set image + set env + annotate + rollout status",
},
runtime: contractRuntime === undefined ? undefined : {
sourceOfTruth: "deploy.json",
containerPort: contractRuntime.containerPort,
healthPath: contractRuntime.healthPath,
memory: contractRuntime.memory,
health: contractRuntime.health,
},
validation: [
"D601 registry /v2 manifest exists for the commit tag before mutation",
"D601 Docker-pulled image labels match service id, source repo, source commit, and Dockerfile",