fix: 修正 PaC 相同 revision 状态判定
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,53 @@ function digestFromImage(value) {
|
||||
return value.split("@").slice(1).join("@") || null;
|
||||
}
|
||||
|
||||
function normalizeGitopsRevisionRelation(value) {
|
||||
const relation = record(value);
|
||||
const expectedRevision = stringOrNull(relation.expectedRevision);
|
||||
const observedRevision = stringOrNull(relation.observedRevision);
|
||||
const validRevision = /^[0-9a-f]{7,64}$/iu;
|
||||
if (expectedRevision === null
|
||||
|| observedRevision === null
|
||||
|| expectedRevision !== observedRevision
|
||||
|| !validRevision.test(expectedRevision)) {
|
||||
return relation;
|
||||
}
|
||||
|
||||
const originalReason = stringOrNull(relation.reason);
|
||||
const warnings = Array.isArray(relation.warnings) ? [...relation.warnings] : [];
|
||||
if (originalReason === "owning-git-fetch-failed"
|
||||
&& !warnings.some((item) => record(item).code === "pac-owning-git-fetch-failed")) {
|
||||
warnings.push({
|
||||
warning: true,
|
||||
blocking: false,
|
||||
code: "pac-owning-git-fetch-failed",
|
||||
object: { kind: "gitops-revision", id: "owning-git" },
|
||||
configPath: "live:gitops-revision",
|
||||
reason: originalReason,
|
||||
mutation: false,
|
||||
valuesPrinted: false,
|
||||
});
|
||||
}
|
||||
|
||||
const graphProven = relation.relation === "exact" && relation.proof === "owning-git-commit-graph";
|
||||
const directlyProven = relation.relation === "exact" && relation.proof === "direct-revision-equality";
|
||||
return {
|
||||
...relation,
|
||||
relation: "exact",
|
||||
expectedRevision,
|
||||
observedRevision,
|
||||
proof: graphProven || directlyProven ? relation.proof : "direct-revision-equality",
|
||||
reason: graphProven ? "commits-equal" : "revisions-equal",
|
||||
contextReason: graphProven
|
||||
? null
|
||||
: directlyProven
|
||||
? stringOrNull(relation.contextReason)
|
||||
: originalReason,
|
||||
warnings,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function exactSkipMarker(records, event) {
|
||||
return [...records].reverse().find((item) => item.event === event
|
||||
&& item.status === "skipped"
|
||||
@@ -633,7 +680,7 @@ function evaluatePacStatus(inputValue) {
|
||||
const argo = record(input.argo);
|
||||
const runtime = record(input.runtime);
|
||||
const sourceObservation = record(artifact.sourceObservation);
|
||||
const revisionRelation = record(argo.revisionRelation);
|
||||
const revisionRelation = normalizeGitopsRevisionRelation(argo.revisionRelation);
|
||||
const gitopsRelation = stringOrNull(revisionRelation.relation) || "unknown";
|
||||
const sourceCommit = stringOrNull(input.sourceCommit);
|
||||
const registryDigest = stringOrNull(input.registryDigest);
|
||||
@@ -1017,6 +1064,16 @@ function runPacStatusFixtureChecks() {
|
||||
expectedCode: "pac-ready-gitops-exact",
|
||||
input: fixtureInput({ artifact: { imageStatus: "built", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: publishedCatalog() }, argo: exactArgo, runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 } }),
|
||||
},
|
||||
{
|
||||
id: "equal-revisions-ignore-owning-git-fetch-failure",
|
||||
expectedOk: true,
|
||||
expectedCode: "pac-ready-gitops-exact",
|
||||
input: fixtureInput({
|
||||
artifact: { imageStatus: "built", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: publishedCatalog() },
|
||||
argo: { ...exactArgo, revisionRelation: { relation: "unknown", expectedRevision: revision, observedRevision: revision, proof: "unavailable", reason: "owning-git-fetch-failed", fetchOk: false } },
|
||||
runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "gitops-only-registry-not-configured",
|
||||
expectedOk: true,
|
||||
@@ -1041,6 +1098,16 @@ function runPacStatusFixtureChecks() {
|
||||
expectedCode: "pac-argo-revision-stale",
|
||||
input: fixtureInput({ artifact: { imageStatus: "built", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: publishedCatalog() }, argo: { ...exactArgo, revision: "d".repeat(40), revisionRelation: { relation: "stale", expectedRevision: revision, observedRevision: "d".repeat(40) } }, runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 } }),
|
||||
},
|
||||
{
|
||||
id: "different-revisions-fetch-failure-remains-unknown",
|
||||
expectedOk: false,
|
||||
expectedCode: "pac-argo-revision-unknown",
|
||||
input: fixtureInput({
|
||||
artifact: { imageStatus: "built", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: publishedCatalog() },
|
||||
argo: { ...exactArgo, revision: "d".repeat(40), revisionRelation: { relation: "unknown", expectedRevision: revision, observedRevision: "d".repeat(40), proof: "unavailable", reason: "owning-git-fetch-failed", fetchOk: false } },
|
||||
runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "delivery-gitops-missing",
|
||||
expectedOk: false,
|
||||
@@ -1245,6 +1312,7 @@ module.exports = {
|
||||
evaluatePacStatus,
|
||||
extractPacArtifactEvidence,
|
||||
extractPacSourceObservation,
|
||||
normalizeGitopsRevisionRelation,
|
||||
parsePacLogRecords,
|
||||
runPacStatusFixtureChecks,
|
||||
pipelineRunMatchesConsumer,
|
||||
|
||||
@@ -15,6 +15,7 @@ const evaluator = createRequire(import.meta.url)("../native/cicd/pac-status-eval
|
||||
evaluatePacAdmissionState: (input: Record<string, unknown>) => Record<string, any>;
|
||||
evaluatePacStatus: (input: Record<string, unknown>) => Record<string, any>;
|
||||
extractPacArtifactEvidence: (records: unknown[], logText: string) => Record<string, any>;
|
||||
normalizeGitopsRevisionRelation: (input: Record<string, unknown>) => Record<string, any>;
|
||||
taskTerminalRecord: (taskRun: Record<string, unknown>) => Record<string, any> | null;
|
||||
runPacStatusFixtureChecks: () => { ok: boolean; checks: Array<{ id: string; ok: boolean }> };
|
||||
};
|
||||
@@ -347,11 +348,41 @@ test("AgentRun rendered delivery plan and real TaskRun terminals close the statu
|
||||
test("provenance fixtures fail closed for pre-bootstrap, forged marker, wrong SA, and policy mismatch", () => {
|
||||
const result = evaluator.runPacStatusFixtureChecks();
|
||||
expect(result.ok).toBe(true);
|
||||
for (const id of ["gitops-only-registry-not-configured", "image-registry-missing-fails-closed", "admission-provenance-verified", "pre-bootstrap-run-fails-closed", "forged-name-candidate-fails-closed", "forged-label-candidate-fails-closed", "forged-pipeline-ref-candidate-fails-closed", "wrong-service-account-fails-closed", "policy-identity-mismatch-fails-closed"]) {
|
||||
for (const id of ["gitops-only-registry-not-configured", "image-registry-missing-fails-closed", "equal-revisions-ignore-owning-git-fetch-failure", "different-revisions-fetch-failure-remains-unknown", "admission-provenance-verified", "pre-bootstrap-run-fails-closed", "forged-name-candidate-fails-closed", "forged-label-candidate-fails-closed", "forged-pipeline-ref-candidate-fails-closed", "wrong-service-account-fails-closed", "policy-identity-mismatch-fails-closed"]) {
|
||||
expect(result.checks.find((item) => item.id === id)?.ok).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("equal GitOps revisions stay exact when the supplemental owning Git fetch fails", () => {
|
||||
const revision = "b".repeat(40);
|
||||
const raw = {
|
||||
relation: "unknown",
|
||||
expectedRevision: revision,
|
||||
observedRevision: revision,
|
||||
proof: "unavailable",
|
||||
reason: "owning-git-fetch-failed",
|
||||
fetchOk: false,
|
||||
};
|
||||
const normalized = evaluator.normalizeGitopsRevisionRelation(raw);
|
||||
expect(normalized).toMatchObject({
|
||||
relation: "exact",
|
||||
proof: "direct-revision-equality",
|
||||
reason: "revisions-equal",
|
||||
contextReason: "owning-git-fetch-failed",
|
||||
warnings: [{
|
||||
warning: true,
|
||||
blocking: false,
|
||||
code: "pac-owning-git-fetch-failed",
|
||||
mutation: false,
|
||||
valuesPrinted: false,
|
||||
}],
|
||||
valuesPrinted: false,
|
||||
});
|
||||
expect(evaluator.normalizeGitopsRevisionRelation(normalized)).toEqual(normalized);
|
||||
const remote = readFileSync(resolve(root, "scripts/src/platform-infra-pipelines-as-code-remote.sh"), "utf8");
|
||||
expect(remote).toContain("normalizeGitopsRevisionRelation({");
|
||||
});
|
||||
|
||||
test("history evaluates admission and default RBAC in each consumer namespace", () => {
|
||||
const remote = readFileSync(resolve(root, "scripts/src/platform-infra-pipelines-as-code-remote.sh"), "utf8");
|
||||
expect(remote).toContain("consumer.admissionState = admissionStateForConsumer(consumer)");
|
||||
|
||||
@@ -1258,7 +1258,8 @@ NODE
|
||||
UNIDESK_PAC_GITOPS_PROOF="$proof" \
|
||||
UNIDESK_PAC_GITOPS_REASON="$reason" \
|
||||
UNIDESK_PAC_GITOPS_FETCH_OK="$fetch_ok" node <<'NODE'
|
||||
process.stdout.write(JSON.stringify({
|
||||
const { normalizeGitopsRevisionRelation } = require(process.env.UNIDESK_PAC_EVALUATOR_PATH);
|
||||
process.stdout.write(JSON.stringify(normalizeGitopsRevisionRelation({
|
||||
relation: process.env.UNIDESK_PAC_GITOPS_RELATION || 'unknown',
|
||||
expectedRevision: process.env.UNIDESK_PAC_GITOPS_EXPECTED || null,
|
||||
observedRevision: process.env.UNIDESK_PAC_GITOPS_OBSERVED || null,
|
||||
@@ -1272,7 +1273,7 @@ process.stdout.write(JSON.stringify({
|
||||
reason: process.env.UNIDESK_PAC_GITOPS_REASON || null,
|
||||
fetchOk: process.env.UNIDESK_PAC_GITOPS_FETCH_OK === 'true',
|
||||
valuesPrinted: false,
|
||||
}));
|
||||
})));
|
||||
NODE
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user