fix: 统一 HWLAB PaC Pipeline provenance 合同
This commit is contained in:
@@ -11,10 +11,12 @@ import { hwlabRuntimeLaneSpecForNode, isHwlabRuntimeLane, type HwlabRuntimeLaneS
|
||||
import { nodeRuntimeGitopsRoot } from "./hwlab-node/cleanup";
|
||||
import { nodeRuntimePipelinePostprocessScript } from "./hwlab-node/render";
|
||||
import { nodeRuntimeRenderOverlay } from "./hwlab-node/web-probe";
|
||||
import { hwlabRuntimePipelineProvenance } from "./hwlab-node/pipeline-provenance";
|
||||
import { applyNodeRuntimeDeployYamlOverlay } from "./hwlab-node/deploy-overlay";
|
||||
import type { RenderedCliResult } from "./output";
|
||||
import { renderedCliResult } from "./agentrun/render";
|
||||
import { stableJsonSha256, stableJsonValue } from "./stable-json";
|
||||
import { pipelineProvenanceAnnotations, pipelineProvenanceFromManifest, withPipelineProvenanceAnnotations } from "./pipeline-provenance";
|
||||
|
||||
export type PacSourceArtifactMode = "embedded-pipeline-spec" | "remote-pipeline-annotation";
|
||||
export type PacSourceArtifactRenderer = "agentrun-control-plane" | "hwlab-runtime-lane";
|
||||
@@ -167,13 +169,6 @@ export function pacSourceArtifactSafeError(error: unknown): PacSourceArtifactSaf
|
||||
};
|
||||
}
|
||||
|
||||
const provenanceKeys = {
|
||||
configRef: "unidesk.ai/owning-config-ref",
|
||||
effectiveConfigSha256: "unidesk.ai/effective-config-sha256",
|
||||
renderer: "unidesk.ai/source-artifact-renderer",
|
||||
mode: "unidesk.ai/source-artifact-mode",
|
||||
} as const;
|
||||
|
||||
const knownDriftPathKeys = new Set([
|
||||
"accessModes", "annotations", "apiVersion", "args", "command", "computeResources", "default", "description", "dnsPolicy", "env", "envFrom",
|
||||
"executionMode", "finally", "fsGroup", "generateName", "hostNetwork", "image", "kind", "labels", "length", "metadata", "mode", "mountPath", "name", "namespace", "onError", "operator",
|
||||
@@ -442,7 +437,12 @@ function renderDesiredArtifact(binding: PacSourceArtifactBinding, sourceWorktree
|
||||
const rendered = sourceArtifact.renderer === "agentrun-control-plane"
|
||||
? renderAgentRunDesiredPipeline(binding)
|
||||
: renderHwlabDesiredPipeline(binding, sourceWorktree);
|
||||
const pipeline = withPipelineProvenance(rendered.pipeline, rendered.provenance);
|
||||
const pipeline = sourceArtifact.renderer === "hwlab-runtime-lane"
|
||||
? rendered.pipeline
|
||||
: withPipelineProvenanceAnnotations(rendered.pipeline, rendered.provenance);
|
||||
if (!provenanceEquals(provenanceFromManifest(pipeline), rendered.provenance)) {
|
||||
throw new Error(`${sourceArtifact.renderer} did not render the declared Pipeline provenance contract`);
|
||||
}
|
||||
const desiredSpec = requiredRecord(pipeline.spec, "rendered Pipeline.spec");
|
||||
assertPipelineIdentity(pipeline, binding);
|
||||
const pipelineRun = sourceArtifact.mode === "embedded-pipeline-spec"
|
||||
@@ -485,8 +485,11 @@ function renderAgentRunDesiredPipeline(binding: PacSourceArtifactBinding): { pip
|
||||
function renderHwlabDesiredPipeline(binding: PacSourceArtifactBinding, sourceWorktree: string): { pipeline: Record<string, unknown>; provenance: PacSourceArtifactProvenance } {
|
||||
if (!isHwlabRuntimeLane(binding.consumer.lane)) throw new Error(`HWLAB source artifact lane ${binding.consumer.lane} is not declared`);
|
||||
const spec = hwlabRuntimeLaneSpecForNode(binding.consumer.lane, binding.consumer.node);
|
||||
const expectedConfigRef = `config/hwlab-node-lanes.yaml#lanes.${spec.lane}.targets.${spec.nodeId}`;
|
||||
assertConfigRef(binding.consumer.sourceArtifact.configRef, expectedConfigRef);
|
||||
const provenance = hwlabRuntimePipelineProvenance(spec);
|
||||
if (binding.consumer.sourceArtifact.renderer !== provenance.renderer || binding.consumer.sourceArtifact.mode !== provenance.mode) {
|
||||
throw new Error(`sourceArtifact renderer/mode must match ${provenance.configRef}.pipelineProvenance`);
|
||||
}
|
||||
assertConfigRef(binding.consumer.sourceArtifact.configRef, provenance.configRef);
|
||||
assertBindingIdentity(binding, {
|
||||
node: spec.nodeId,
|
||||
lane: spec.lane,
|
||||
@@ -500,12 +503,7 @@ function renderHwlabDesiredPipeline(binding: PacSourceArtifactBinding, sourceWor
|
||||
const pipeline = renderHwlabPipelineFromOwningYaml(spec, sourceWorktree);
|
||||
return {
|
||||
pipeline,
|
||||
provenance: {
|
||||
configRef: expectedConfigRef,
|
||||
effectiveConfigSha256: stableJsonSha256(spec),
|
||||
renderer: "hwlab-runtime-lane",
|
||||
mode: binding.consumer.sourceArtifact.mode,
|
||||
},
|
||||
provenance,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -547,7 +545,7 @@ function renderHwlabPipelineFromOwningYaml(spec: HwlabRuntimeLaneSpec, sourceWor
|
||||
`overlay_b64=${shellQuote(overlay)}`,
|
||||
...nodeRuntimePipelinePostprocessScript(),
|
||||
].join("\n");
|
||||
runChecked("sh", ["-c", postprocess], temporarySource, 120_000, "HWLAB UniDesk domain postprocess/verify renderer");
|
||||
runChecked("sh", [], temporarySource, 120_000, "HWLAB UniDesk domain postprocess/verify renderer", postprocess);
|
||||
const path = resolve(output, spec.tektonDir, "pipeline.yaml");
|
||||
if (!existsSync(path)) throw new Error(`HWLAB domain renderer did not produce ${path}`);
|
||||
return parseYamlRecord(readFileSync(path, "utf8"), path);
|
||||
@@ -637,10 +635,7 @@ function pipelineRunAnnotations(binding: PacSourceArtifactBinding, provenance: P
|
||||
"pipelinesascode.tekton.dev/on-target-branch": `[${branch}]`,
|
||||
"pipelinesascode.tekton.dev/on-cel-expression": `event == 'push' && target_branch == '${branch}' && node == '${binding.consumer.node}'`,
|
||||
"pipelinesascode.tekton.dev/max-keep-runs": String(binding.consumer.sourceArtifact.maxKeepRuns),
|
||||
[provenanceKeys.configRef]: provenance.configRef,
|
||||
[provenanceKeys.effectiveConfigSha256]: provenance.effectiveConfigSha256,
|
||||
[provenanceKeys.renderer]: provenance.renderer,
|
||||
[provenanceKeys.mode]: binding.consumer.sourceArtifact.mode,
|
||||
...pipelineProvenanceAnnotations(provenance),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -745,20 +740,6 @@ function pipelineRunWorkspaces(binding: PacSourceArtifactBinding, desiredSpec: R
|
||||
});
|
||||
}
|
||||
|
||||
function withPipelineProvenance(pipeline: Record<string, unknown>, provenance: PacSourceArtifactProvenance): Record<string, unknown> {
|
||||
const next = structuredClone(pipeline);
|
||||
const metadata = requiredRecord(next.metadata, "rendered Pipeline.metadata");
|
||||
const annotations = isRecord(metadata.annotations) ? metadata.annotations : {};
|
||||
metadata.annotations = {
|
||||
...annotations,
|
||||
[provenanceKeys.configRef]: provenance.configRef,
|
||||
[provenanceKeys.effectiveConfigSha256]: provenance.effectiveConfigSha256,
|
||||
[provenanceKeys.renderer]: provenance.renderer,
|
||||
[provenanceKeys.mode]: provenance.mode,
|
||||
};
|
||||
return next;
|
||||
}
|
||||
|
||||
function inspectSourceArtifact(sourceWorktree: string, binding: PacSourceArtifactBinding, desired: DesiredArtifact): SourceInspection {
|
||||
const sourceArtifact = binding.consumer.sourceArtifact;
|
||||
const pipelineRunPath = safeArtifactPath(sourceWorktree, sourceArtifact.pipelineRunPath);
|
||||
@@ -932,14 +913,11 @@ function assertPipelineIdentity(pipeline: Record<string, unknown>, binding: PacS
|
||||
}
|
||||
|
||||
function provenanceFromManifest(manifest: Record<string, unknown>): PacSourceArtifactProvenance | null {
|
||||
const metadata = isRecord(manifest.metadata) ? manifest.metadata : {};
|
||||
const annotations = isRecord(metadata.annotations) ? metadata.annotations : {};
|
||||
const configRef = annotations[provenanceKeys.configRef];
|
||||
const effectiveConfigSha256 = annotations[provenanceKeys.effectiveConfigSha256];
|
||||
const renderer = annotations[provenanceKeys.renderer];
|
||||
const mode = annotations[provenanceKeys.mode];
|
||||
if (typeof configRef !== "string" || typeof effectiveConfigSha256 !== "string" || (renderer !== "agentrun-control-plane" && renderer !== "hwlab-runtime-lane") || (mode !== "embedded-pipeline-spec" && mode !== "remote-pipeline-annotation")) return null;
|
||||
return { configRef, effectiveConfigSha256, renderer, mode };
|
||||
const provenance = pipelineProvenanceFromManifest(manifest);
|
||||
if (provenance === null) return null;
|
||||
if (provenance.renderer !== "agentrun-control-plane" && provenance.renderer !== "hwlab-runtime-lane") return null;
|
||||
if (provenance.mode !== "embedded-pipeline-spec" && provenance.mode !== "remote-pipeline-annotation") return null;
|
||||
return provenance as PacSourceArtifactProvenance;
|
||||
}
|
||||
|
||||
function provenanceEquals(actual: PacSourceArtifactProvenance | null, expected: PacSourceArtifactProvenance): boolean {
|
||||
@@ -1098,9 +1076,13 @@ function git(worktree: string, args: readonly string[]): string {
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
function runChecked(command: string, args: readonly string[], cwd: string, timeout: number, label: string): void {
|
||||
const result = spawnSync(command, [...args], { cwd, encoding: "utf8", timeout, maxBuffer: 32 * 1024 * 1024 });
|
||||
if (result.status !== 0) throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim().slice(-4000)}`);
|
||||
function runChecked(command: string, args: readonly string[], cwd: string, timeout: number, label: string, input?: string): void {
|
||||
const result = spawnSync(command, [...args], { cwd, encoding: "utf8", timeout, maxBuffer: 32 * 1024 * 1024, input });
|
||||
if (result.status !== 0) {
|
||||
const output = [result.stderr, result.stdout].find((value) => typeof value === "string" && value.trim().length > 0)?.trim();
|
||||
const reason = output ?? result.error?.message ?? `exit=${String(result.status)} signal=${String(result.signal)}`;
|
||||
throw new Error(`${label} failed: ${reason.slice(-4000)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function shellQuote(value: string): string {
|
||||
|
||||
Reference in New Issue
Block a user