fix: expose AgentRun branch-follower CI reuse evidence
This commit is contained in:
@@ -18,6 +18,8 @@ const pipelineRunPrefix = process.env.PIPELINE_RUN_PREFIX || "";
|
||||
const argoNamespace = process.env.ARGO_NAMESPACE || "";
|
||||
const argoApplication = process.env.ARGO_APPLICATION || "";
|
||||
const runtimeNamespace = process.env.RUNTIME_NAMESPACE || "";
|
||||
const stateNamespace = process.env.STATE_NAMESPACE || "";
|
||||
const stateConfigMap = process.env.STATE_CONFIGMAP || "";
|
||||
const workloads = parseWorkloads(process.env.WORKLOADS_B64 || "");
|
||||
const healthUrl = process.env.HEALTH_URL || "";
|
||||
const slowTaskSeconds = requiredPositiveIntEnv("SLOW_TASK_SECONDS");
|
||||
@@ -91,8 +93,9 @@ async function ciTaskRunEvidence(commit) {
|
||||
const taskRuns = await getJson(`/apis/tekton.dev/v1/namespaces/${encodeURIComponent(tektonNamespace)}/taskruns?labelSelector=${encodeURIComponent(`tekton.dev/pipelineRun=${pipelineRunName}`)}`, false);
|
||||
const taskSummary = taskRunsSummary(taskRuns);
|
||||
const planArtifacts = planArtifactsEvidence(pipelineRunName);
|
||||
const storedCiConsumption = await storedAgentRunCiConsumption(commit);
|
||||
const buildTaskRunServices = buildTaskServices(taskRuns);
|
||||
const ciConsumption = ciConsumptionSummary(reusePlan.decisions || [], planArtifacts, buildTaskRunServices);
|
||||
const ciConsumption = ciConsumptionSummary(reusePlan.decisions || [], planArtifacts, buildTaskRunServices, storedCiConsumption);
|
||||
return {
|
||||
ok: prStatus.succeeded === true && taskSummary.failedCount === 0 && taskSummary.activeCount === 0 && ciConsumption.ok === true,
|
||||
reusePlan: {
|
||||
@@ -100,6 +103,7 @@ async function ciTaskRunEvidence(commit) {
|
||||
decisionSummary: compactDecisionSummary(reusePlan.decisionSummary),
|
||||
},
|
||||
planArtifacts,
|
||||
storedCiConsumption,
|
||||
ciConsumption,
|
||||
pipelineRun: prStatus,
|
||||
pipeline: {
|
||||
@@ -250,9 +254,55 @@ function planArtifactsEvidence(pipelineRunName) {
|
||||
}
|
||||
}
|
||||
|
||||
function ciConsumptionSummary(decisions, planArtifacts, buildTaskRunServices) {
|
||||
function ciConsumptionSummary(decisions, planArtifacts, buildTaskRunServices, storedCiConsumption) {
|
||||
const skipExpected = decisions.filter((item) => item.skipImageBuild === true).map((item) => item.serviceId).sort();
|
||||
const buildExpected = decisions.filter((item) => item.skipImageBuild !== true).map((item) => item.serviceId).sort();
|
||||
if (/agentrun/u.test(follower) && storedCiConsumption?.present === true && storedCiConsumption.ok !== true) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: storedCiConsumption.reason || "ci-consumption-evidence-missing",
|
||||
source: "branch-follower-compact-state",
|
||||
expected: {
|
||||
skipImageBuildCount: skipExpected.length,
|
||||
buildImageCount: buildExpected.length,
|
||||
},
|
||||
observed: {
|
||||
buildServicesCount: 0,
|
||||
buildTaskRunServices,
|
||||
reusedServicesCount: 0,
|
||||
skippedOrReusedServicesCount: 0,
|
||||
buildSkippedCount: null,
|
||||
},
|
||||
mismatches: [],
|
||||
};
|
||||
}
|
||||
if (storedCiConsumption?.ok === true) {
|
||||
const observed = storedCiConsumption.observed || {};
|
||||
const unexpectedStoredBuild = skipExpected.filter((serviceId) => observed.imageBuildDecision === "buildImage" && serviceId === "agentrun-mgr");
|
||||
const missingStoredBuild = buildExpected.filter((serviceId) => observed.imageBuildDecision !== "buildImage" && serviceId === "agentrun-mgr");
|
||||
const storedOk = unexpectedStoredBuild.length === 0 && missingStoredBuild.length === 0;
|
||||
return {
|
||||
ok: storedOk,
|
||||
reason: storedOk ? "ci-consumed-reuse-plan" : "ci-consumption-mismatch",
|
||||
source: "branch-follower-compact-state",
|
||||
expected: {
|
||||
skipImageBuildCount: skipExpected.length,
|
||||
buildImageCount: buildExpected.length,
|
||||
},
|
||||
observed: {
|
||||
buildServicesCount: numberOrNull(observed.buildServicesCount),
|
||||
buildTaskRunServices,
|
||||
reusedServicesCount: numberOrNull(observed.reusedServicesCount),
|
||||
skippedOrReusedServicesCount: numberOrNull(observed.reusedServicesCount),
|
||||
buildSkippedCount: numberOrNull(observed.buildSkippedCount),
|
||||
imageBuildDecision: str(observed.imageBuildDecision),
|
||||
imageBuildStatus: str(observed.imageBuildStatus),
|
||||
imageBuildCreated: observed.imageBuildCreated === true,
|
||||
imageBuildReused: observed.imageBuildReused === true,
|
||||
},
|
||||
mismatches: compactCiMismatches(unexpectedStoredBuild, missingStoredBuild),
|
||||
};
|
||||
}
|
||||
const buildObserved = uniqueStrings([...strings(planArtifacts?.buildServices), ...buildTaskRunServices]).sort();
|
||||
const reusedObserved = strings(planArtifacts?.reusedServices).sort();
|
||||
const skippedObserved = uniqueStrings(reusedObserved).sort();
|
||||
@@ -278,6 +328,33 @@ function ciConsumptionSummary(decisions, planArtifacts, buildTaskRunServices) {
|
||||
};
|
||||
}
|
||||
|
||||
async function storedAgentRunCiConsumption(commit) {
|
||||
if (!stateNamespace || !stateConfigMap || !follower || !commit) return null;
|
||||
const cm = await getJson(`/api/v1/namespaces/${encodeURIComponent(stateNamespace)}/configmaps/${encodeURIComponent(stateConfigMap)}`, false);
|
||||
const raw = cm?.data?.[follower];
|
||||
if (typeof raw !== "string" || raw.length === 0) return { ok: false, present: false, reason: "compact-state-missing" };
|
||||
const state = parseJson(raw);
|
||||
if (state === null) return { ok: false, present: true, reason: "state-json-parse-failed" };
|
||||
const command = state?.command || {};
|
||||
const payload = command.payload || {};
|
||||
const agentrun = payload.agentrun || {};
|
||||
const sourceCommit = str(agentrun.sourceCommit) || str(command.sourceCommit);
|
||||
const ci = agentrun.ciConsumption || null;
|
||||
if (sourceCommit === null && (!ci || typeof ci !== "object")) return { ok: false, present: true, reason: "ci-consumption-evidence-missing", sourceCommit: null };
|
||||
if (sourceCommit !== commit) return { ok: false, present: true, reason: "state-source-commit-mismatch", sourceCommit };
|
||||
if (!ci || typeof ci !== "object") return { ok: false, present: true, reason: "ci-consumption-evidence-missing", sourceCommit };
|
||||
return {
|
||||
ok: ci.ok === true,
|
||||
present: true,
|
||||
sourceCommit,
|
||||
source: str(ci.source),
|
||||
expected: ci.expected || null,
|
||||
observed: ci.observed || null,
|
||||
mismatches: Array.isArray(ci.mismatches) ? ci.mismatches.slice(0, 6) : [],
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function compactCiMismatches(unexpectedBuild, missingBuild) {
|
||||
return [
|
||||
...(unexpectedBuild.length > 0 ? [{ reason: "expected-skipImageBuild-but-ci-buildServices-includes-service", serviceIds: unexpectedBuild }] : []),
|
||||
@@ -692,6 +769,19 @@ function str(value) {
|
||||
return typeof value === "string" && value.length > 0 ? value : null;
|
||||
}
|
||||
|
||||
function parseJson(value) {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function numberOrNull(value) {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function strings(value) {
|
||||
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user