test: add deploy json executor drift preflight
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user