Merge pull request #1508 from pikasTech/fix/1499-hwlab-pipeline-evidence

feat: add bounded branch-follower pipeline evidence
This commit is contained in:
Lyon
2026-07-04 10:56:04 +08:00
committed by GitHub
13 changed files with 287 additions and 3 deletions
+46 -1
View File
@@ -55,7 +55,10 @@ if (key === "pipelineRun") {
apiVersion: input.apiVersion,
kind: input.kind,
metadata: metadata(input),
spec: { params: Array.isArray(input?.spec?.params) ? input.spec.params : [] },
spec: {
pipelineRef: { name: input?.spec?.pipelineRef?.name || null },
params: Array.isArray(input?.spec?.params) ? input.spec.params : [],
},
status: {
conditions: Array.isArray(input?.status?.conditions) ? input.status.conditions : [],
startTime: input?.status?.startTime || null,
@@ -65,6 +68,48 @@ if (key === "pipelineRun") {
reason: succeeded?.reason || null,
},
};
} else if (key === "pipeline") {
const tasks = Array.isArray(input?.spec?.tasks) ? input.spec.tasks : [];
const runtimeReady = tasks.find((item) => item?.name === "runtime-ready") || null;
const gitopsPromote = tasks.find((item) => item?.name === "gitops-promote") || null;
const gitopsResults = Array.isArray(gitopsPromote?.taskSpec?.results) ? gitopsPromote.taskSpec.results : [];
output = {
apiVersion: input.apiVersion,
kind: input.kind,
metadata: {
name: input?.metadata?.name || null,
namespace: input?.metadata?.namespace || null,
annotations: {
sourceConfig: input?.metadata?.annotations?.["hwlab.pikastech.local/source-config"] || null,
ciContract: input?.metadata?.annotations?.["hwlab.pikastech.local/ci-contract"] || null,
policy: input?.metadata?.annotations?.["hwlab.pikastech.local/policy"] || null,
},
},
spec: {
taskCount: tasks.length,
runtimeReadyTask: {
present: runtimeReady !== null,
name: runtimeReady?.name || null,
runAfter: Array.isArray(runtimeReady?.runAfter) ? runtimeReady.runAfter.slice(0, 6) : [],
when: Array.isArray(runtimeReady?.when)
? runtimeReady.when.slice(0, 4).map((item) => ({
input: item?.input || null,
operator: item?.operator || null,
values: Array.isArray(item?.values) ? item.values.slice(0, 6) : [],
}))
: [],
},
gitopsPromoteTask: {
present: gitopsPromote !== null,
name: gitopsPromote?.name || null,
resultNames: gitopsResults
.map((item) => item?.name || null)
.filter((item) => typeof item === "string")
.slice(0, 8),
runtimeReadyRequiredResult: gitopsResults.some((item) => item?.name === "runtime-ready-required"),
},
},
};
} else if (key === "taskRuns") {
const items = (Array.isArray(input?.items) ? input.items : []).map((item) => {
const succeeded = condition(item, "Succeeded");
+25
View File
@@ -121,6 +121,7 @@ while (Date.now() <= deadline) {
const complete = condition(latest, "Complete");
const failed = condition(latest, "Failed");
const logs = await logsTail();
const summary = parseLastJsonSummary(logs);
const timedOut = !complete && !failed;
const output = {
ok: Boolean(complete) && !timedOut,
@@ -137,6 +138,7 @@ const output = {
conditionReason: complete?.reason || failed?.reason || null,
conditionMessage: complete?.message || failed?.message || null,
logsTail: logs || null,
summary,
statusAuthority: "kubernetes-api-serviceaccount",
parsedDownstreamCliOutput: false,
valuesRedacted: true,
@@ -155,3 +157,26 @@ function requiredPositiveNumber(name) {
if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive number`);
return value;
}
function parseLastJsonSummary(text) {
const lines = String(text || "").split(/\r?\n/u).map((item) => item.trim()).filter(Boolean);
for (let index = lines.length - 1; index >= 0; index -= 1) {
try {
const parsed = JSON.parse(lines[index]);
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return compactValue(parsed, 0);
} catch {
continue;
}
}
return null;
}
function compactValue(value, depth) {
if (typeof value === "string") return value.length <= 240 ? value : `${value.slice(0, 160)} ... ${value.slice(-60)}`;
if (typeof value !== "object" || value === null) return value;
if (Array.isArray(value)) return value.slice(0, 8).map((item) => compactValue(item, depth + 1));
if (depth >= 3) return "[bounded-object]";
const output = {};
for (const [key, child] of Object.entries(value).slice(0, 16)) output[key] = compactValue(child, depth + 1);
return output;
}
@@ -95,6 +95,10 @@ if [ -n "${source_commit}" ] && [ -n "${tekton_namespace}" ] && [ -n "${pipeline
sha12=$(printf '%s' "${source_commit}" | cut -c1-12)
pipeline_run="${pipeline_run_prefix}-${sha12}"
emit_kube_json pipelineRun "/apis/tekton.dev/v1/namespaces/${tekton_namespace}/pipelineruns/${pipeline_run}"
pipeline_ref="$(node -e "const fs=require('node:fs'); try { const value=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(value?.spec?.pipelineRef?.name || ''); } catch {}" "${tmpdir}/pipelineRun.raw" 2>/dev/null || true)"
if [ -n "${pipeline_ref}" ]; then
emit_kube_json pipeline "/apis/tekton.dev/v1/namespaces/${tekton_namespace}/pipelines/${pipeline_ref}"
fi
emit_kube_json taskRuns "/apis/tekton.dev/v1/namespaces/${tekton_namespace}/taskruns?labelSelector=tekton.dev%2FpipelineRun%3D${pipeline_run}"
emit_plan_artifacts "${tekton_namespace}" "${pipeline_run}"
fi
@@ -209,10 +209,12 @@ function compactNativePayload(payload) {
gitMirror: compactGitMirror(value.gitMirror),
reuseConfig: compactReuseConfig(value.reuseConfig),
tekton: compactTekton(value.tekton),
pipeline: compactPipeline(value.pipeline),
taskRuns: compactTaskRuns(value.taskRuns),
planArtifacts: compactPlanArtifacts(value.planArtifacts),
argo: compactArgo(value.argo),
runtime: compactRuntime(value.runtime),
refreshEvidence: compactRefreshEvidence(recordOrNull(recordOrNull(value.nativeCapabilities)?.controlPlaneRefresh)),
errors: arrayStrings(value.errors).slice(0, 5),
statusAuthority: stringOrNull(value.statusAuthority),
parsedDownstreamCliOutput: false,
@@ -264,6 +266,7 @@ function compactTekton(tekton) {
if (value === null) return null;
return {
name: stringOrNull(value.name),
pipelineRefName: stringOrNull(value.pipelineRefName),
succeeded: value.succeeded === true ? true : value.succeeded === false ? false : null,
reason: stringOrNull(value.reason),
startTime: stringOrNull(value.startTime),
@@ -272,6 +275,33 @@ function compactTekton(tekton) {
};
}
function compactPipeline(pipeline) {
const value = recordOrNull(pipeline);
if (value === null) return null;
return {
metadata: recordOrNull(value.metadata),
spec: recordOrNull(value.spec),
};
}
function compactRefreshEvidence(refresh) {
const value = recordOrNull(refresh);
const summary = recordOrNull(value?.summary);
if (value === null || summary === null) return null;
return {
jobName: stringOrNull(value.jobName) ?? stringOrNull(summary.jobName),
namespace: stringOrNull(value.namespace) ?? stringOrNull(summary.namespace),
status: stringOrNull(summary.status),
pipeline: stringOrNull(summary.pipeline),
sourceCommit: stringOrNull(summary.sourceCommit),
sourceStageRef: stringOrNull(summary.sourceStageRef),
elapsedMs: numberOrNull(summary.elapsedMs),
sourceAuthority: stringOrNull(summary.sourceAuthority),
statusAuthority: stringOrNull(summary.statusAuthority),
parsedDownstreamCliOutput: false,
};
}
function compactArgo(argo) {
const value = recordOrNull(argo);
if (value === null) return null;