Skip to content

feat(cli): .policies holds what each cluster's Kyverno policies need, and deploy applies it - #578

Merged
bmscomp merged 10 commits into
mainfrom
feat/labels-pod-section
Nov 1, 2026
Merged

bmscomp merged 10 commits into
mainfrom
feat/labels-pod-section

Conversation

@bmscomp

@bmscomp bmscomp commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Merge #577 first. This branch carries #577's two commits; after #577 squash-merges, I'll merge main in.

Why

kates deploy gave namespaces their labels from .labels, but had no way to label pods or pass the chart values a cluster's Kyverno policies need. On a cluster with an Enforce "pods need team" policy, the namespaces were created and then helm upgrade --install was refused at admission. #577's kyverno check could find the gap but deploy couldn't act on it.

What

.policies, git-ignored (.gitignore), written mode 0600, with a warning if git tracks it. It has one environment per kube context:

environments:
  kind-panda:
    namespaceLabels: {cost-center: cc-42, owner: platform}
    podLabels: {team: payments, tier: data}
    values:
      kates: {global: {imageRegistry: registry.internal/kates}}
    unresolved: [...]   # rules the last check couldn't fix

kates deploy reads the environment of the context it deploys to:

  • namespaceLabels sit between .labels and KATES_NAMESPACE_LABELS in precedence.
  • podLabels go under every value that labels each chart's pods. chartPodLabelKeys covers every chart deploy installs; a test holds it equal to label_args in scripts/check-chart-policies.sh, which proves in CI that those values reach every pod.
  • values.<chart> goes in a file passed after deploy's own values files, so deploy's --set values still win.
  • unresolved entries are a warning.
  • --policies-file names another file.

kates kyverno check

  • Applies the same environment to each render.
  • Checks each namespace as the Namespace deploy would create there, so namespace label rules are evaluated too.
  • --write-policies fills the environment. Kyverno reports only the first missing label of a rule, so the check reads the failing rule's pattern for every label it requires. A label's value comes from --label, else the one exact value the pattern names. ?*, a | b, a refused value, and every non-label rule go under unresolved with the suggested fix.

.labels is still read as namespace labels, with a one-line hint that .policies holds them now. --labels-file is unchanged.

Not covered

  • kates auto, kates kyverno apply and the admin-less gate's scratch installs don't apply podLabels/values yet. They do get .policies' namespace labels, through the shared label reader.
  • Third-party charts deploy installs (cert-manager, Kyverno) get no pod labels.
  • An existing namespace's own labels aren't read by the check.
  • Rules on Kafka broker pods are still not visible offline (the render has the Kafka CR, not the pods).

Tests

  • cli: go test ./..., go vet, golangci-lint --new-from-rev=origin/main (0 issues), govulncheck (none). New tests:
    • file validation, and environments kept on save
    • pod labels under every key
    • namespace label precedence
    • pattern extraction, and withFindings
    • deploy passing the values file after its own (kafka component)
  • scripts/check-cli-style.sh, check-cli-compat.sh, check-book-style.sh, gen-book-index.py --check, gen-version-matrix.sh --check
  • Real run on a scratch Kind cluster (kyvpol, deleted after) with Kyverno 1.17.2 and two Enforce ClusterPolicies: Namespace needs cost-center: cc-42 and owner: ?*; Pod needs team: ?* and tier: data.
    • Run 1, kyverno check --chart kates -n kates --label team=payments --write-policies: exit 1. It wrote cost-center: cc-42, team: payments and tier: data, and listed owner as unresolved. Another environment in the file was kept, and the file ended up 0600.
    • Run 2, after filling owner: exit 0, "Every rule passes (6 passed)".
    • Admission agreed. A bare kates namespace was refused and the labelled one created. The kates chart with only tier set was denied 5 times on a server dry-run, and with both labels it applied with no errors.
  • Not run: ./mvnw test (no backend change; feat(cli): kyverno check covers the stack and writes the values it misses #577's run on the same backend tree was 1,790 green), a full kates deploy on the scratch cluster, and the book render.

…sses

PR #540 merged into feat/kyverno-check after #539 had been squashed onto
main, so it never reached main. This is that change on today's main.

kates kyverno check --chart <name> (repeatable) and --stack check the
charts kates deploy installs: strimzi-operator, monitoring, kafka-cluster,
connect-cluster, mirror-maker2 and kates. --stack takes the ones deploy's
--with-* flags select, each in the namespace deploy's --*-ns flags resolve
(-n puts every chart in one), after the helm dependency command deploy
runs on it. -f and --set apply to every chart, or to one as <chart>=...

--write-values <dir> writes <chart>-kyverno.yaml for each chart with a
failed rule: a missing pod label set to the value --label gives, under
every value that labels the chart's pods (the keys label_args in
scripts/check-chart-policies.sh uses, held equal by a test), and every
other rule as a commented kyvernoPolicy.exceptions entry, or a note where
the chart has none. Values already in the file are kept.

inNamespace now edits YAML nodes: kube-prometheus-stack renders a label
twice when commonLabels and a component's podLabels both set it, which
helm and kyverno accept and a map decode refused.

#541's exception scope carries over to every chart: the admission
controller's exception namespace is read once, exceptions Kyverno would
ignore are dropped, the render's own PolicyException counts only where
Kyverno accepts exceptions, and each chart's report has exceptionNote.

-o json is now a list, one report per chart, each naming its chart.
… and deploy applies it

.policies is a git-ignored YAML file with one environment per kube
context: namespaceLabels, podLabels, values per chart, and the rules the
last check left unresolved. Kates writes it 0600 and warns when git
tracks it.

kates deploy reads the environment of the context it deploys to.
namespaceLabels label its namespaces, after .labels and before
KATES_NAMESPACE_LABELS and --namespace-label. podLabels go under every
value that labels each chart's pods (chartPodLabelKeys, held equal to
label_args in scripts/check-chart-policies.sh for every chart deploy
installs). values go to each chart in a file passed after deploy's own.
Unresolved rules are a warning. --policies-file names another file.

kates kyverno check applies the current context's environment to each
render the way deploy does, and checks each namespace as the Namespace
deploy would create, with its labels. --write-policies writes that
environment: a rule that failed on a missing label sets every label its
policy's pattern requires there (Kyverno reports only the first), from
--label, else the one exact value the pattern names. A label with no
known value, a value a policy refuses, and every other failed rule go
under unresolved with the fix the check suggests. Other environments
and values already set are kept.

.labels is still read as namespace labels, with a hint that .policies
holds them now.
…lues file

The notes kyverno check writes into <chart>-kyverno.yaml quote the
cluster's policy messages. Only \n was folded, so a \r, NEL, LS or PS in a
message ended the comment and the rest parsed as values. Every whitespace
run now folds to a space, after invalid UTF-8 is replaced.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Code graph out of date: Health delta baseline: last indexed commit 0a08117, 7 commit(s) behind this PR's base. Impact and hotspot numbers below may be wrong.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Not checked: tests were not run; no formal proof of the changed code.


Graphify review — findings

Adds a per-kube-context .policies file that feeds namespace labels and per-chart Helm values into kates deploy. It is re-read once the target context is pinned, and each chart's values are appended after deploy's own -f files so they take precedence. In namespaceUserLabels, its namespaceLabels are merged after .labels but before KATES_NAMESPACE_LABELS and --namespace-label, and a hint tells users .labels is superseded. Also adds a Kyverno check that resolves the chart stack against those policies, with the CLI reference and deployment docs updated to match.

Worth a look

  • .policies namespaceLabels bypass label validation and the reserved-domain check — cli/cmd/deploy_labels.go:76 · Escalate · medium
    • NOT verified (no proof, no reproducing execution) — needs human review
  • Kafka stack chart uses chart-root-relative values path with chart prefix — cli/cmd/kyverno_check.go:62 · Escalate · medium
    • NOT verified (no proof, no reproducing execution) — needs human review

Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 976 functions depend on the 603 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: runDeploy() — 20 callers, 30 callees
  • worse: namespaceUserLabels() — 11 callers, 6 callees
  • new: writeKyvernoValues() — 4 callers, 6 callees
  • new: recoverStuckReleases() — 6 callers, 4 callees
  • worse: runComponents() — 7 callers, 3 callees
  • new: recoverStuckRelease() — 1 callers, 8 callees
  • new: checkKyvernoChart() — 1 callers, 7 callees
  • new: runDeployAndReport() — 1 callers, 6 callees
  • …and 1 more — each is listed as a finding

Verification — 976 functions in the blast radius were not formally verified this run (proofs are advisory here).

Health delta baseline: last indexed commit 0a08117, 7 commit(s) behind this PR's base.

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 862 function(s) in the blast radius were not formally verified this run

Docs that may be stale (advisory)

…and 2 more.

· 4 grounded finding(s) anchored inline below; 5 more finding(s) on lines outside this diff (see the check run).

Comment thread cli/cmd/deploy_labels.go Resolved
Comment thread cli/cmd/kyverno_check.go Resolved
Comment thread cli/cmd/kyverno_check.go Resolved
Comment thread cli/cmd/kyverno_check.go Resolved
…to Check the Labels lands

The first namespaceUserLabels call in runDeploy only validates, so its
results go unassigned (ineffassign). The Check the Labels heading gets
the explicit id its GitHub slug has, so Quarto renders the id the new
.policies section links to.
…into named helpers

namespaceUserLabels reads .labels and .policies through addLabelsFileLabels
and addPoliciesNamespaceLabels. checkKyvernoChart runs kyverno through
runKyvernoApply, renderKyvernoChartCheck prints exceptions through
renderKyvernoExceptions, and writeKyvernoValues reads its file through
readValuesFile. setPodLabels is the one walk that puts pod labels under a
chart's label values, shared by writeKyvernoValues and .policies'
chartValues, which each had their own copy.
… the checkout alone

Review fixes:
- --stack and --chart also cover cdc-postgresql, apicurio-registry,
  kafka-ui and kates-chaos, selected by deploy's --with-kafka-connect,
  --with-schema-registry, --with-kafka-ui and --with-chaos.
- The check takes deploy's --topology, --db-ns, --chaos-ns and --ui-ns, so
  a stack's namespaces are deploy's under either topology.
- A chart's helm dependency command runs only when helm dependency list
  finds a dependency missing: it rewrites Chart.lock and charts/ and needs
  the network.
- --label is held to the label syntax, and needs --write-values.
- Cluster-scoped policies and every namespace's exceptions are listed once
  a run, not once per namespace; a refused -A is asked once.
- The default chart is chosen by name, not by its place in the list.
- Kyverno with exceptions on but no --exceptionNamespace accepts none
  (Kyverno 1.17's NewExceptionSelector); the note now says so.
- The book's Check the Labels heading gets its explicit id.
…nto feat/labels-pod-section

# Conflicts:
#	cli/cmd/kyverno_check.go
#	docs/book/10-cli-reference.md
# Conflicts:
#	cli/cmd/kyverno_check.go
#	cli/cmd/kyverno_check_test.go
#	docs/book/10-cli-reference.md
@bmscomp
bmscomp merged commit edc5aaa into main Nov 1, 2026
63 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant