Merge pull request #2145 from pikasTech/fix/2039-revert-pac-admission-fence
fix(cicd): 语义回退 PaC admission 业务阻断
This commit is contained in:
@@ -38,6 +38,11 @@ test("admission contract rejects malformed required declarations instead of sile
|
||||
const duplicatedRequiredConsumers = duplicatedServiceAccount.consumers.filter((item: any) => item.deliveryProvenance !== undefined);
|
||||
duplicatedRequiredConsumers[1].deliveryProvenance.executionServiceAccountName = duplicatedRequiredConsumers[0].deliveryProvenance.executionServiceAccountName;
|
||||
expect(() => parsePacAdmissionContract(duplicatedServiceAccount)).toThrow("executionServiceAccountName must be unique per target");
|
||||
|
||||
const denyingAdmission = structuredClone(source);
|
||||
denyingAdmission.deliveryProvenance.admission.failurePolicy = "Fail";
|
||||
denyingAdmission.deliveryProvenance.admission.validationActions = ["Deny"];
|
||||
expect(() => parsePacAdmissionContract(denyingAdmission)).toThrow("validationActions must be exactly [Warn, Audit]");
|
||||
});
|
||||
|
||||
test("admission queue transition contract rejects absent sources, unknown Tekton states, and duplicate transitions", () => {
|
||||
@@ -55,7 +60,7 @@ test("admission queue transition contract rejects absent sources, unknown Tekton
|
||||
expect(() => parsePacAdmissionContract(duplicate)).toThrow("deliveryProvenance.queueTransition.allowed must be unique");
|
||||
});
|
||||
|
||||
test("versioned admission desired state protects controller proof, candidate identity, params, and execution SA", () => {
|
||||
test("versioned admission desired state reports controller proof and candidate drift without denying delivery", () => {
|
||||
const contract = readPacAdmissionContract();
|
||||
const owningYaml = Bun.YAML.parse(readFileSync(resolve(root, "config/platform-infra/pipelines-as-code.yaml"), "utf8")) as any;
|
||||
expect(owningYaml.deliveryProvenance.terminalRoles).toBeUndefined();
|
||||
@@ -69,15 +74,15 @@ test("versioned admission desired state protects controller proof, candidate ide
|
||||
toState: "started",
|
||||
}]);
|
||||
expect(contract.child.enabled).toBe(false);
|
||||
expect(contract.consumers.map((item) => item.id)).toEqual(["agentrun-nc01-v02", "platform-infra-sub2rank-nc01", "selfmedia-nc01"]);
|
||||
expect(contract.consumers.map((item) => item.id)).toEqual(["agentrun-nc01-v02", "platform-infra-sub2rank-nc01", "selfmedia-nc01", "selfmedia-production-nc01", "pikaoa-test-nc01"]);
|
||||
const items = documents(renderPacAdmissionDesiredFragment("NC01"));
|
||||
expect(items.map((item) => item.kind)).toEqual(["ValidatingAdmissionPolicy", "ValidatingAdmissionPolicyBinding"]);
|
||||
const policy = items[0];
|
||||
const expressions = policy.spec.validations.map((item: any) => item.expression).join("\n");
|
||||
const messages = policy.spec.validations.map((item: any) => item.message).join("\n");
|
||||
expect(policy.spec.failurePolicy).toBe("Fail");
|
||||
expect(policy.spec.failurePolicy).toBe("Ignore");
|
||||
expect(policy.spec.matchConstraints.matchPolicy).toBe("Equivalent");
|
||||
expect(items[1].spec.validationActions).toEqual(["Deny"]);
|
||||
expect(items[1].spec.validationActions).toEqual(["Warn", "Audit"]);
|
||||
expect(policy.metadata.annotations["argocd.argoproj.io/sync-wave"]).toBe("0");
|
||||
expect(items[1].metadata.annotations["argocd.argoproj.io/sync-wave"]).toBe("1");
|
||||
expect(expressions).toContain("request.userInfo.username");
|
||||
@@ -134,8 +139,8 @@ test("versioned admission desired state protects controller proof, candidate ide
|
||||
|
||||
test("required consumer default RBAC has one automatic desired owner per namespace and no Tekton mutation verbs", () => {
|
||||
const rbac = documents(renderPacConsumerRbacDesiredFragment("NC01"));
|
||||
expect(rbac.map((item) => item.kind)).toEqual(["Role", "RoleBinding", "Role", "RoleBinding", "Role", "RoleBinding"]);
|
||||
expect(rbac.filter((item) => item.kind === "Role").map((item) => item.metadata.namespace)).toEqual(["agentrun-ci", "devops-infra", "selfmedia-ci"]);
|
||||
expect(rbac.map((item) => item.kind)).toEqual(["Role", "RoleBinding", "Role", "RoleBinding", "Role", "RoleBinding", "Role", "RoleBinding"]);
|
||||
expect(rbac.filter((item) => item.kind === "Role").map((item) => item.metadata.namespace)).toEqual(["agentrun-ci", "devops-infra", "selfmedia-ci", "pikaoa-ci"]);
|
||||
for (const role of rbac.filter((item) => item.kind === "Role")) {
|
||||
expect(role.metadata.annotations["argocd.argoproj.io/sync-wave"]).toBe("-1");
|
||||
expect(role.metadata.annotations["unidesk.ai/effective-config-sha256"]).toBe(pacConsumerRbacDesiredIdentity("NC01").configSha256);
|
||||
@@ -150,6 +155,8 @@ test("required consumer default RBAC has one automatic desired owner per namespa
|
||||
"RoleBinding",
|
||||
"Role",
|
||||
"RoleBinding",
|
||||
"Role",
|
||||
"RoleBinding",
|
||||
"ValidatingAdmissionPolicy",
|
||||
"ValidatingAdmissionPolicyBinding",
|
||||
"ServiceAccount",
|
||||
@@ -158,7 +165,7 @@ test("required consumer default RBAC has one automatic desired owner per namespa
|
||||
]);
|
||||
});
|
||||
|
||||
test("live admission evaluator requires exact desired hashes, observed generation, default-SA loss, and a new resource epoch", () => {
|
||||
test("live admission evaluator reports exact desired drift as non-blocking warnings", () => {
|
||||
const contract = readPacAdmissionContract();
|
||||
const admission = documents(renderPacAdmissionDesiredFragment("NC01"));
|
||||
const rbac = documents(renderPacConsumerRbacDesiredFragment("NC01"));
|
||||
@@ -174,7 +181,7 @@ test("live admission evaluator requires exact desired hashes, observed generatio
|
||||
rbacConfigSha256: rbac[0].metadata.annotations["unidesk.ai/effective-config-sha256"],
|
||||
};
|
||||
const input = { policy, binding, role: rbac[0], roleBinding: rbac[1], namespace: "agentrun-ci", defaultCanMutate: false, expected };
|
||||
expect(evaluator.evaluatePacAdmissionState(input)).toMatchObject({ ready: true, activeAfter: "2026-01-01T00:00:01Z", defaultCanMutate: false });
|
||||
expect(evaluator.evaluatePacAdmissionState(input)).toMatchObject({ ready: true, blocking: false, warning: false, activeAfter: "2026-01-01T00:00:01Z", defaultCanMutate: false });
|
||||
expect(evaluator.evaluatePacAdmissionState({
|
||||
...input,
|
||||
policy: {
|
||||
@@ -189,7 +196,7 @@ test("live admission evaluator requires exact desired hashes, observed generatio
|
||||
},
|
||||
},
|
||||
},
|
||||
})).toMatchObject({ ready: false, reasons: expect.arrayContaining(["policy-expression-warning"]) });
|
||||
})).toMatchObject({ ready: false, blocking: false, warning: true, reasons: expect.arrayContaining(["policy-expression-warning"]) });
|
||||
const apiDefaultedPolicy = {
|
||||
...policy,
|
||||
spec: {
|
||||
@@ -206,10 +213,10 @@ test("live admission evaluator requires exact desired hashes, observed generatio
|
||||
ready: true,
|
||||
liveSpecSha256: expected.configSha256,
|
||||
});
|
||||
expect(evaluator.evaluatePacAdmissionState({ ...input, defaultCanMutate: true })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["default-serviceaccount-can-mutate-tekton-runs"]) });
|
||||
expect(evaluator.evaluatePacAdmissionState({ ...input, defaultCanMutate: null })).toMatchObject({ ready: false, defaultCanMutate: null, reasons: expect.arrayContaining(["default-serviceaccount-mutation-probe-unavailable"]) });
|
||||
expect(evaluator.evaluatePacAdmissionState({ ...input, role: { ...rbac[0], rules: [rbac[0].rules[0]] } })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["default-role-rules-mismatch"]) });
|
||||
expect(evaluator.evaluatePacAdmissionState({ ...input, expected: { ...expected, configSha256: `sha256:${"0".repeat(64)}` } })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["policy-config-sha256-mismatch"]) });
|
||||
expect(evaluator.evaluatePacAdmissionState({ ...input, defaultCanMutate: true })).toMatchObject({ ready: false, blocking: false, warning: true, reasons: expect.arrayContaining(["default-serviceaccount-can-mutate-tekton-runs"]) });
|
||||
expect(evaluator.evaluatePacAdmissionState({ ...input, defaultCanMutate: null })).toMatchObject({ ready: false, blocking: false, warning: true, defaultCanMutate: null, reasons: expect.arrayContaining(["default-serviceaccount-mutation-probe-unavailable"]) });
|
||||
expect(evaluator.evaluatePacAdmissionState({ ...input, role: { ...rbac[0], rules: [rbac[0].rules[0]] } })).toMatchObject({ ready: false, blocking: false, warning: true, reasons: expect.arrayContaining(["default-role-rules-mismatch"]) });
|
||||
expect(evaluator.evaluatePacAdmissionState({ ...input, expected: { ...expected, configSha256: `sha256:${"0".repeat(64)}` } })).toMatchObject({ ready: false, blocking: false, warning: true, reasons: expect.arrayContaining(["policy-config-sha256-mismatch"]) });
|
||||
const mutatedPolicy = {
|
||||
...policy,
|
||||
spec: {
|
||||
@@ -345,10 +352,10 @@ test("AgentRun rendered delivery plan and real TaskRun terminals close the statu
|
||||
})).toMatchObject({ ok: true, code: "pac-ready-gitops-exact" });
|
||||
});
|
||||
|
||||
test("provenance fixtures fail closed for pre-bootstrap, forged marker, wrong SA, and policy mismatch", () => {
|
||||
test("provenance fixtures keep outer PaC delivery eligible while reporting admission warnings", () => {
|
||||
const result = evaluator.runPacStatusFixtureChecks();
|
||||
expect(result.ok).toBe(true);
|
||||
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"]) {
|
||||
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-warning-only", "marker-mismatch-warning-only", "candidate-label-warning-only", "candidate-pipeline-ref-warning-only", "service-account-mismatch-warning-only", "policy-identity-mismatch-warning-only"]) {
|
||||
expect(result.checks.find((item) => item.id === id)?.ok).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -33,8 +33,8 @@ export interface PacAdmissionContract {
|
||||
readonly allowedQueueTransitions: readonly PacAdmissionQueueTransitionContract[];
|
||||
readonly policyName: string;
|
||||
readonly bindingName: string;
|
||||
readonly failurePolicy: "Fail";
|
||||
readonly validationActions: readonly ["Deny"];
|
||||
readonly failurePolicy: "Ignore";
|
||||
readonly validationActions: readonly ["Warn", "Audit"];
|
||||
readonly child: {
|
||||
readonly enabled: false;
|
||||
readonly parentNameAnnotation: string;
|
||||
@@ -83,7 +83,9 @@ export function parsePacAdmissionContract(source: Record<string, unknown>): PacA
|
||||
const transitionKeys = new Set(allowedQueueTransitions.map((transition) => [transition.fromSpecStatus, transition.toSpecStatus, transition.fromState, transition.toState].join("\u0000")));
|
||||
if (transitionKeys.size !== allowedQueueTransitions.length) throw new Error("deliveryProvenance.queueTransition.allowed must be unique");
|
||||
const validationActions = stringArray(admission.validationActions, "deliveryProvenance.admission.validationActions");
|
||||
if (validationActions.length !== 1 || validationActions[0] !== "Deny") throw new Error("deliveryProvenance.admission.validationActions must be exactly [Deny]");
|
||||
if (validationActions.length !== 2 || validationActions[0] !== "Warn" || validationActions[1] !== "Audit") {
|
||||
throw new Error("deliveryProvenance.admission.validationActions must be exactly [Warn, Audit]");
|
||||
}
|
||||
const consumers = recordArray(root.consumers, "consumers").flatMap((consumer, index) => {
|
||||
if (consumer.deliveryProvenance === undefined) return [];
|
||||
const value = record(consumer.deliveryProvenance, `consumers[${index}].deliveryProvenance`);
|
||||
@@ -127,8 +129,8 @@ export function parsePacAdmissionContract(source: Record<string, unknown>): PacA
|
||||
allowedQueueTransitions,
|
||||
policyName,
|
||||
bindingName,
|
||||
failurePolicy: literal(admission.failurePolicy, "deliveryProvenance.admission.failurePolicy", "Fail"),
|
||||
validationActions: ["Deny"],
|
||||
failurePolicy: literal(admission.failurePolicy, "deliveryProvenance.admission.failurePolicy", "Ignore"),
|
||||
validationActions: ["Warn", "Audit"],
|
||||
child: {
|
||||
enabled: literal(child.enabled, "deliveryProvenance.child.enabled", false),
|
||||
parentNameAnnotation: required(child.parentNameAnnotation, "deliveryProvenance.child.parentNameAnnotation"),
|
||||
|
||||
@@ -177,6 +177,12 @@ test("PaC apply checks the Gitea repository before dry-run return and cluster mu
|
||||
expect(remote).toContain('"admission":{"applied":%s,"ready":%s}');
|
||||
expect(remote).toContain('"admissionProvenance":%s');
|
||||
expect(remote).toContain('"error":"gitea-repository-missing"');
|
||||
const statusAction = remote.slice(remote.indexOf("status_action() {"), remote.indexOf("history_action() {"));
|
||||
expect(statusAction).toContain('[ "$bootstrap_ready" = "true" ]');
|
||||
expect(statusAction).toContain("output.code = 'pac-consumer-bootstrap-not-ready'");
|
||||
expect(statusAction).toContain("output.ok = false");
|
||||
expect(statusAction).toContain("code: 'pac-admission-provenance-not-ready'");
|
||||
expect(statusAction).toContain("blocking: false");
|
||||
});
|
||||
|
||||
test("platform bootstrap help advertises the composite entry before low-level apply", async () => {
|
||||
@@ -210,6 +216,35 @@ test("PaC bootstrap projects typed stages and compact JSON", () => {
|
||||
expect(rendered).toContain("confirm:");
|
||||
});
|
||||
|
||||
test("PaC bootstrap keeps admission drift as a non-blocking warning", () => {
|
||||
const result = {
|
||||
ok: true,
|
||||
action: "platform-infra-pipelines-as-code-bootstrap",
|
||||
mutation: true,
|
||||
mode: "confirmed",
|
||||
target: { id: "NC01" },
|
||||
consumer: { id: "widgets", node: "NC01", lane: "v01" },
|
||||
repository: { id: "widgets", namespace: "ci" },
|
||||
mirrorRepository: { key: mirror.key, upstreamRepository: mirror.upstream.repository, upstreamBranch: mirror.upstream.branch, owner: mirror.gitea.owner, repo: mirror.gitea.name },
|
||||
githubPreflight: { ok: true, mutation: false, repositories: [{ key: mirror.key, code: "github-upstream-available", ok: true }] },
|
||||
giteaBootstrap: { ok: true, mutation: false, mode: "skipped", blockers: [] },
|
||||
pipelinesAsCodeApply: {
|
||||
ok: true,
|
||||
mutation: true,
|
||||
mode: "confirmed",
|
||||
remote: {
|
||||
ok: true,
|
||||
admission: { applied: true, ready: false },
|
||||
admissionProvenance: { policyName: "unidesk-pac-delivery-provenance-v2", reasons: ["policy-generation-not-observed"] },
|
||||
},
|
||||
},
|
||||
};
|
||||
const projection = projectPacBootstrapResult(result);
|
||||
expect(projection.ok).toBe(true);
|
||||
expect((projection.stages as Record<string, unknown>[]).find((item) => item.id === "pac-admission")).toMatchObject({ ok: true, state: "warning", blocking: false });
|
||||
expect(projection.warnings).toEqual([expect.objectContaining({ code: "pac-admission-provenance-not-ready", blocking: false })]);
|
||||
});
|
||||
|
||||
test("PaC bootstrap returns fix-yaml next for zero YAML matches", () => {
|
||||
const result = pacBootstrapYamlMatchFailure("NC01", "widgets", new Error("cannot resolve repository: no exact match"));
|
||||
expect(projectPacBootstrapResult(result)).toBe(result);
|
||||
|
||||
@@ -70,6 +70,7 @@ export function projectPacBootstrapResult(result: Record<string, unknown>): Reco
|
||||
const apply = record(result.pipelinesAsCodeApply);
|
||||
const remote = record(apply.remote);
|
||||
const admission = record(remote.admission);
|
||||
const admissionProvenance = record(remote.admissionProvenance);
|
||||
const blockers = bootstrapBlockers(githubPreflight, gitea, apply, remote);
|
||||
const dryRun = result.mode === "dry-run";
|
||||
const repositoryMissing = remote.error === "gitea-repository-missing";
|
||||
@@ -78,6 +79,16 @@ export function projectPacBootstrapResult(result: Record<string, unknown>): Reco
|
||||
const giteaSkipped = gitea.mode === "skipped";
|
||||
const applySkipped = apply.mode === "skipped";
|
||||
const applyReady = apply.ok === true || (dryRun && repositoryMissing);
|
||||
const warnings = [
|
||||
...records(result.warnings),
|
||||
...(applySkipped || dryRun || repositoryMissing || admission.ready === true ? [] : [{
|
||||
object: { kind: "ValidatingAdmissionPolicy", id: stringValue(admissionProvenance.policyName, "PaC admission") },
|
||||
blocking: false,
|
||||
code: "pac-admission-provenance-not-ready",
|
||||
configPath: "config/platform-infra/pipelines-as-code.yaml#deliveryProvenance.admission",
|
||||
reasons: Array.isArray(admissionProvenance.reasons) ? admissionProvenance.reasons : [],
|
||||
}]),
|
||||
];
|
||||
const next = bootstrapNext(result, blockers);
|
||||
return {
|
||||
ok: blockers.length === 0 && giteaReady && applyReady,
|
||||
@@ -102,12 +113,12 @@ export function projectPacBootstrapResult(result: Record<string, unknown>): Reco
|
||||
stages: [
|
||||
{ id: "gitea-init", ok: giteaSkipped ? null : giteaReady, state: giteaSkipped ? "skipped" : giteaReady ? (dryRun ? "planned" : "ready") : "failed", mutation: gitea.mutation === true },
|
||||
{ id: "pac-controller", ok: applySkipped ? null : applyReady, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : applyReady ? (dryRun ? "planned" : "ready") : "failed", mutation: apply.mutation === true },
|
||||
{ id: "pac-admission", ok: applySkipped ? null : dryRun ? true : admission.ready === true, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : dryRun ? "planned" : admission.ready === true ? "ready" : "failed", mutation: admission.applied === true },
|
||||
{ id: "pac-admission", ok: applySkipped ? null : true, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : dryRun ? "planned" : admission.ready === true ? "ready" : "warning", blocking: false, mutation: admission.applied === true },
|
||||
{ id: "tekton-consumer", ok: applySkipped ? null : applyReady, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : applyReady ? (dryRun ? "planned" : "ready") : "failed", mutation: apply.mutation === true },
|
||||
{ id: "gitops-argo", ok: applySkipped ? null : applyReady, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : applyReady ? (dryRun ? "planned" : "ready") : "failed", mutation: apply.mutation === true },
|
||||
],
|
||||
repository: { id: repository.id, namespace: repository.namespace },
|
||||
warnings: result.warnings,
|
||||
warnings,
|
||||
blockers,
|
||||
next,
|
||||
valuesPrinted: false,
|
||||
|
||||
@@ -363,15 +363,13 @@ apply_action() {
|
||||
hook_id=$(ensure_webhook)
|
||||
ready=false
|
||||
if wait_ready; then ready=true; fi
|
||||
prepare_admission_provenance_state
|
||||
admission_ready=true
|
||||
if [ "${UNIDESK_PAC_DELIVERY_PROVENANCE_REQUIRED:-0}" = "1" ]; then
|
||||
admission_ready=false
|
||||
if wait_admission_ready; then admission_ready=true; fi
|
||||
else
|
||||
prepare_admission_provenance_state
|
||||
admission_ready=$(printf '%s' "$UNIDESK_PAC_ADMISSION_STATE_JSON" | node -e 'let input="";process.stdin.on("data",chunk=>input+=chunk);process.stdin.on("end",()=>process.stdout.write(JSON.parse(input).ready===true?"true":"false"));')
|
||||
fi
|
||||
ok=false
|
||||
if [ "$ready" = true ] && [ "$admission_ready" = true ]; then ok=true; fi
|
||||
if [ "$ready" = true ]; then ok=true; fi
|
||||
printf '{"ok":%s,"mode":"confirmed","mutation":true,"controllerReady":%s,"admission":{"applied":%s,"ready":%s},"admissionProvenance":%s,"argoBootstrap":%s,"preflight":{"repositoryExists":true,"httpStatus":"2xx","repository":"%s/%s","valuesPrinted":false},"webhook":{"present":true,"id":"%s","url":"%s"},"repository":"%s","namespace":"%s","valuesPrinted":false}\n' "$ok" "$ready" "$admission_applied" "$admission_ready" "$UNIDESK_PAC_ADMISSION_STATE_JSON" "$argo_bootstrap" "$(json_string "$UNIDESK_PAC_GITEA_OWNER")" "$(json_string "$UNIDESK_PAC_GITEA_REPO")" "$(json_string "$hook_id")" "$(json_string "$UNIDESK_PAC_WEBHOOK_URL")" "$(json_string "$UNIDESK_PAC_REPOSITORY_NAME")" "$(json_string "$UNIDESK_PAC_TARGET_NAMESPACE")"
|
||||
}
|
||||
|
||||
@@ -436,19 +434,6 @@ prepare_admission_provenance_state() {
|
||||
export UNIDESK_PAC_ADMISSION_STATE_JSON
|
||||
}
|
||||
|
||||
wait_admission_ready() {
|
||||
timeout="${UNIDESK_PAC_WAIT_TIMEOUT_SECONDS:-55}"
|
||||
end=$(( $(now_epoch) + timeout ))
|
||||
while :; do
|
||||
prepare_admission_provenance_state
|
||||
if printf '%s' "$UNIDESK_PAC_ADMISSION_STATE_JSON" | node -e 'let input="";process.stdin.on("data",chunk=>input+=chunk);process.stdin.on("end",()=>process.exit(JSON.parse(input).ready===true?0:1));'; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$(now_epoch)" -ge "$end" ]; then return 1; fi
|
||||
sleep 2
|
||||
done
|
||||
}
|
||||
|
||||
pipeline_rows() {
|
||||
payload_file=$(mktemp)
|
||||
kubectl -n "$UNIDESK_PAC_TARGET_NAMESPACE" get pipelinerun -o json >"$payload_file" 2>/dev/null || printf '{"items":[]}' >"$payload_file"
|
||||
@@ -624,7 +609,7 @@ function defaultCanMutate(namespace) {
|
||||
|
||||
function admissionStateForConsumer(consumer) {
|
||||
const expected = consumer.deliveryProvenance;
|
||||
if (!expected || expected.required !== true) return { configured: false, required: false, ready: null, reasons: ['consumer-admission-provenance-not-required'], valuesPrinted: false };
|
||||
if (!expected || expected.required !== true) return { configured: false, required: false, ready: null, blocking: false, warning: false, warnings: [], reasons: ['consumer-admission-provenance-not-required'], valuesPrinted: false };
|
||||
admissionPolicy ||= kubectlJson(['get', 'validatingadmissionpolicy', expected.policyName, '-o', 'json'], {}, 'admission-policy');
|
||||
admissionBinding ||= kubectlJson(['get', 'validatingadmissionpolicybinding', expected.bindingName, '-o', 'json'], {}, 'admission-binding');
|
||||
if (!admissionRbac.has(consumer.namespace)) {
|
||||
@@ -1561,6 +1546,9 @@ if (!passwordPresent) reasons.push('argocd-repository-secret-password-missing');
|
||||
process.stdout.write(JSON.stringify({
|
||||
configured: runnerConfigured || argoConfigured,
|
||||
ready: reasons.length === 0,
|
||||
blocking: false,
|
||||
warning: reasons.length > 0,
|
||||
warnings: reasons,
|
||||
runner: {
|
||||
configured: runnerConfigured,
|
||||
serviceAccount: runnerName || null,
|
||||
@@ -1597,7 +1585,6 @@ status_action() {
|
||||
repository_condition=$(condition_status "$UNIDESK_PAC_TARGET_NAMESPACE" repository "$UNIDESK_PAC_REPOSITORY_NAME")
|
||||
prepare_admission_provenance_state
|
||||
consumer_bootstrap=$(consumer_bootstrap_state)
|
||||
bootstrap_ready=$(UNIDESK_PAC_BOOTSTRAP_STATE="$consumer_bootstrap" node -e 'const s=JSON.parse(process.env.UNIDESK_PAC_BOOTSTRAP_STATE||"{}"); process.stdout.write(s.configured===true&&s.ready!==true?"false":"true")')
|
||||
pipelines=$(pipeline_rows)
|
||||
latest=$(printf '%s' "$pipelines" | node -e 'const fs=require("fs"); const a=JSON.parse(fs.readFileSync(0,"utf8")||"[]"); process.stdout.write(a[0]?.name||"")')
|
||||
export UNIDESK_PAC_TARGET_PIPELINERUN="$latest"
|
||||
@@ -1621,39 +1608,30 @@ NODE
|
||||
)
|
||||
runtime=$(json_normalize "$(runtime_summary)")
|
||||
diagnostics=$(cicd_diagnostics "$pipelines" "$artifact" "$argo" "$runtime")
|
||||
admission_ready=$(UNIDESK_PAC_STATE="$UNIDESK_PAC_ADMISSION_STATE_JSON" node -e 'const s=JSON.parse(process.env.UNIDESK_PAC_STATE||"{}"); process.stdout.write(s.ready===true?"true":"false")')
|
||||
diagnostics=$(UNIDESK_PAC_DIAGNOSTICS="$diagnostics" UNIDESK_PAC_STATE="$UNIDESK_PAC_ADMISSION_STATE_JSON" UNIDESK_PAC_BOOTSTRAP_STATE="$consumer_bootstrap" node <<'NODE'
|
||||
const diagnostics = JSON.parse(process.env.UNIDESK_PAC_DIAGNOSTICS || '{}');
|
||||
const admissionProvenance = JSON.parse(process.env.UNIDESK_PAC_STATE || '{}');
|
||||
const consumerBootstrap = JSON.parse(process.env.UNIDESK_PAC_BOOTSTRAP_STATE || '{}');
|
||||
const output = { ...diagnostics, admissionProvenance, consumerBootstrap, valuesPrinted: false };
|
||||
if (process.env.UNIDESK_PAC_DELIVERY_PROVENANCE_REQUIRED === '1' && admissionProvenance.ready !== true) {
|
||||
process.stdout.write(JSON.stringify({
|
||||
...diagnostics,
|
||||
ok: false,
|
||||
code: 'pac-admission-provenance-not-ready',
|
||||
phase: 'admission-provenance-not-ready',
|
||||
hint: 'required PaC admission policy, binding, desired RBAC, or live default-SA mutation gate is not ready',
|
||||
admissionProvenance,
|
||||
valuesPrinted: false,
|
||||
}));
|
||||
} else if (consumerBootstrap.configured === true && consumerBootstrap.ready !== true) {
|
||||
process.stdout.write(JSON.stringify({
|
||||
...diagnostics,
|
||||
ok: false,
|
||||
code: 'pac-consumer-bootstrap-not-ready',
|
||||
phase: 'consumer-bootstrap-not-ready',
|
||||
hint: 'declared runner ServiceAccount/RoleBinding or Argo repository credential is missing or drifted',
|
||||
admissionProvenance,
|
||||
consumerBootstrap,
|
||||
valuesPrinted: false,
|
||||
}));
|
||||
} else {
|
||||
process.stdout.write(JSON.stringify({ ...diagnostics, admissionProvenance, consumerBootstrap, valuesPrinted: false }));
|
||||
output.warnings = [...(Array.isArray(output.warnings) ? output.warnings : []), {
|
||||
code: 'pac-admission-provenance-not-ready',
|
||||
blocking: false,
|
||||
hint: 'PaC admission policy, binding, desired RBAC, or live default-SA observation is drifted; delivery continues',
|
||||
reasons: Array.isArray(admissionProvenance.reasons) ? admissionProvenance.reasons : [],
|
||||
}];
|
||||
}
|
||||
if (consumerBootstrap.configured === true && consumerBootstrap.ready !== true) {
|
||||
output.ok = false;
|
||||
output.code = 'pac-consumer-bootstrap-not-ready';
|
||||
output.phase = 'consumer-bootstrap-not-ready';
|
||||
output.hint = 'declared runner ServiceAccount/RoleBinding or Argo repository credential is missing or drifted';
|
||||
}
|
||||
process.stdout.write(JSON.stringify(output));
|
||||
NODE
|
||||
)
|
||||
printf '{"ok":%s,"crdPresent":%s,"controllerReady":"%s","admissionProvenance":%s,"consumerBootstrap":%s,"repository":{"name":"%s","repo":"%s/%s","url":"%s","condition":"%s"},"repositoryCondition":"%s","webhooks":%s,"pipelineRuns":%s,"taskRuns":%s,"artifact":%s,"argo":%s,"runtime":%s,"diagnostics":%s,"valuesPrinted":false}\n' \
|
||||
"$( [ -n "$crd" ] && [ "$controller_ready" != "0/0" ] && [ "$bootstrap_ready" = "true" ] && { [ "${UNIDESK_PAC_DELIVERY_PROVENANCE_REQUIRED:-0}" != "1" ] || [ "$admission_ready" = "true" ]; } && echo true || echo false )" \
|
||||
"$( [ -n "$crd" ] && [ "$controller_ready" != "0/0" ] && [ "$bootstrap_ready" = "true" ] && echo true || echo false )" \
|
||||
"$( [ -n "$crd" ] && echo true || echo false )" \
|
||||
"$(json_string "$controller_ready")" \
|
||||
"$UNIDESK_PAC_ADMISSION_STATE_JSON" \
|
||||
|
||||
Reference in New Issue
Block a user