fix(cicd): 回退 PaC admission 业务阻断

This commit is contained in:
Codex
2026-07-15 08:40:48 +02:00
parent 11979c86b0
commit 1f3283ffac
8 changed files with 193 additions and 65 deletions
@@ -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);
}
});
+7 -5
View File
@@ -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"),
@@ -210,6 +210,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,7 +1608,6 @@ 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 || '{}');
@@ -1629,20 +1615,23 @@ const consumerBootstrap = JSON.parse(process.env.UNIDESK_PAC_BOOTSTRAP_STATE ||
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',
warnings: [...(Array.isArray(diagnostics.warnings) ? diagnostics.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 : [],
}],
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',
warnings: [...(Array.isArray(diagnostics.warnings) ? diagnostics.warnings : []), {
code: 'pac-consumer-bootstrap-not-ready',
blocking: false,
hint: 'declared runner ServiceAccount/RoleBinding or Argo repository credential is missing or drifted; status remains observable',
}],
admissionProvenance,
consumerBootstrap,
valuesPrinted: false,
@@ -1653,7 +1642,7 @@ if (process.env.UNIDESK_PAC_DELIVERY_PROVENANCE_REQUIRED === '1' && admissionPro
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" ] && echo true || echo false )" \
"$( [ -n "$crd" ] && echo true || echo false )" \
"$(json_string "$controller_ready")" \
"$UNIDESK_PAC_ADMISSION_STATE_JSON" \