Skip to content

feat(scripts): add reproducible Kyverno and CNPG demo clusters - #1402

Open
nadaverell wants to merge 5 commits into
mainfrom
feat/integration-demo-clusters
Open

feat(scripts): add reproducible Kyverno and CNPG demo clusters#1402
nadaverell wants to merge 5 commits into
mainfrom
feat/integration-demo-clusters

Conversation

@nadaverell

@nadaverell nadaverell commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a kind-based Kyverno demo with live admission, legacy and modern policy APIs, dual PolicyException shapes, and real PolicyReport production
  • add a CloudNativePG demo with four 2/2 clusters covering healthy, real WAL-archiving failure, and frozen terminal-state rendering, plus shared-plural collision fixtures
  • expose standard and alternate scenarios through Make targets and document when to use frozen, live, openreports, and modern-only modes

Safeguards

  • pin every Helm operation to the demo kube context
  • fail when required controllers, reports, clusters, Poolers, or scenario invariants do not materialize
  • verify report migration across namespaced and cluster-scoped resources
  • delete only the three named CNPG frozen fixtures before thawing, preserving independently created Backup objects
  • calculate fixture timestamps consistently on macOS and Linux

Verification

  • make test
  • make tsc
  • make build
  • bash -n scripts/kyverno-demo.sh scripts/cnpg-demo.sh
  • ShellCheck for both scripts
  • Kyverno: fresh bootstrap, generated NetworkPolicy, context isolation, idempotent rerun, openreports migration, and modern-only transition
  • CNPG: fresh frozen bootstrap, idempotent rebuild, live transition, refreeze, real WAL failure, healthy-baseline protection, and preservation of a non-fixture Backup across live reruns

Visual testing is skipped because this changes developer fixture tooling and documentation, not application UI.

Tradeoffs

  • these workflows are developer-only and require kind, kubectl, Helm, Docker, and, for CNPG, Python 3
  • modern-only intentionally leaves the current Kyverno admission controller crashlooping after legacy CRDs are removed; it exists to model the future API surface for read-path testing
  • frozen CNPG mode stops the operator so terminal phases remain stable; live mode keeps the operator running and intentionally omits those synthetic terminal phases and Backup rows

Note

Low Risk
Developer-only bash/YAML and documentation; no runtime application or production deployment paths change.

Overview
Adds kind-based developer demos alongside the existing GitOps/Crossplane flows so policy and CloudNativePG UI work can be visual-tested against curated cluster state instead of arbitrary kube contexts.

Kyverno (scripts/kyverno-demo.sh + fixtures): bootstraps Kyverno 1.18.2 with legacy and modern policy APIs, enforcement-posture edge cases, dual-shape PolicyExceptions, workloads that drive real PolicyReports, plus optional openreports (empty wgpolicyk8s.io vs populated openreports.io) and modern-only (legacy CRD removal for 1.20-style detection, with documented admission crashloop). Make targets: kyverno-demo, -down, -status.

CloudNativePG (scripts/cnpg-demo.sh + fixtures): four 2/2 clusters with distinct badges (healthy, real WAL-archiving failure, two frozen unrecoverable phases), Pooler/ScheduledBackup/Backups, and Velero/KubeBlocks plural-collision CRs. Supports frozen (operator scaled down, status patched), live (operator up, no synthetic terminal/backup rows), refreeze/thaw, with guards around backup ordering and baseline contamination. Make targets: cnpg-demo, cnpg-demo-live, -down, -status.

Docs: CLAUDE.md documents when to use each demo; Makefile help lists demo clusters; docs/STRUCTURE.md broadens scripts/ description to “demo-cluster” orchestration.

Reviewed by Cursor Bugbot for commit abd558e. Bugbot is set up for automated code reviews on this repo. Configure here.

Bootstraps a kind cluster with Kyverno 1.18.2 and curated policy fixtures,
following the gitops-demo pattern. Without it, anyone testing policy UI is
testing against whatever cluster they happen to be pointed at, and several
non-obvious states are not reproducible by guesswork.

The fixtures encode the states that are hard to get right rather than a
minimal smoke set:

  - The four enforcement-posture cases. A policy declaring Deny with
    admission evaluation disabled (blocks nothing), and one with NO
    validationActions at all (which Kyverno treats as Deny, undocumented
    upstream). Both are verifiable by hand and the README shows how.
  - A MutatingPolicy with mutateExisting and admission off — the case that
    used to render "Inactive" while rewriting the cluster. Note its default
    is false, unlike admission/background, so it has to be set explicitly.
  - Both API families installed together, since family selection, the
    sidebar grouping and the legacy-vs-modern renderer split only have
    meaning during migration. PolicyException exists in both, same Kind and
    plural, different specs.
  - Two DeletingPolicies on six-hourly and every-minute crons, so the table
    shows "Never run" beside a recent timestamp within two minutes of
    bootstrap rather than needing a hand-patched status.
  - Real workloads, one passing and one failing, so the background scanner
    produces ~40 reports across four distinct results[].source values —
    which is the engine-taxonomy case made visible.

Two subcommands cover states that need a chart reconfiguration:
`openreports` leaves wgpolicyk8s.io served but empty while the data lands in
openreports.io (verified: 0 vs 37), and `modern-only` reproduces the 1.20 API
surface.

`modern-only` prints a warning before it runs, and the README leads with it:
Kyverno 1.18.2's admission controller crashloops without the legacy CRDs
because it sanity-checks for them at startup. That is upstream behaviour, and
anyone who hits it without warning will think they broke the cluster.

The webhook readiness gate is not incidental. `helm --wait` returns before the
admission webhook accepts connections, and applying a policy in that window
fails with "connection refused" — which is how the first run of this script
failed. The gate waits for endpoints and then round-trips a real policy
through a server-side dry-run, since endpoint readiness alone still races the
TLS cert.
CNPG had the problem `make gitops-demo` exists to solve and no answer: anyone
testing CNPG UI changes was testing against whatever cluster was in their
current context, usually one healthy Postgres with none of the variety that
matters.

`make cnpg-demo` builds a kind cluster with four clusters that all reach 2/2
Ready and render four different badges — Healthy, WAL Archiving Failing, and
Unrecoverable twice. The uniform instance count is the point: every difference
is invisible to counts, which is the class of bug the fixture exists to catch.
Plus a Pooler, a plugin-method ScheduledBackup, three Backups, and Velero and
KubeBlocks CRs for the shared `backups` and `clusters` plurals — with one
engine installed, a guard that matches everything and a guard that matches the
right group behave identically.

The payload is the ordering, and every constraint below was found by running
the script rather than by reasoning about it. Each one fails in a way that
looks like success:

Freezing the operator does not hold a status. Each Postgres pod runs an
instance manager that writes cluster status independently, so a patched
ContinuousArchiving=False reverts within ~10 minutes with the operator scaled
to zero. `status.phase` survives; conditions and readyInstances do not.

So the WAL failure is caused, not asserted — but only after the cluster is up.
CNPG verifies the object store during bootstrap, so a cluster created with an
unroutable endpoint never becomes Ready at all. That still yields
ContinuousArchiving=False, which is the convincing wrong answer: the condition
looks right on a cluster that is simply broken. The check therefore requires
both the condition AND 2/2 Ready, because asserting the condition alone passes
on the broken version — as it did, and reported success.

Backups must be created after the freeze, not merely patched after it. The
operator attempts a Backup, and one pointed at a cluster with no backup
configuration fails and stamps LastBackupSucceeded=False on it. The pristine
baseline silently rendered "Backup Failed" — via a resource attached to it
rather than anything wrong with it.

A Pooler must not share a name with one of its cluster's Services. CNPG creates
<cluster>-rw/-ro/-r, and a Pooler creates a Service of its own name, so
pg-healthy-rw refused to reconcile forever with "invalid ownership for managed
resources" — no status, no condition, no event, just a CR that looks slow.

Thawing is not scaling back up. The operator builds its PKI from its own
webhook configurations at startup, so with them deleted it crash-loops on
ensurePKI. `thaw` re-applies the manifest first.

Timestamps are computed relative to now. A hardcoded expiry rendered "Expired"
rather than "24d remaining" within months of being written.

`live` skips the freeze for v2/v3 work that needs a reconciling controller —
real failovers and backup runs, at the cost of hand-written phases.

Verified end-to-end from an empty machine, then against Radar: four badges as
documented, audit evaluated 4 / passed 1 with only the plugin-method cluster
passing, four CNPG issues, and both backup engines rendering their own column
sets on one cluster.
Same slot as the GitOps and Crossplane entries, with the instance-manager
gotcha called out — anyone editing the fixtures needs to know it before they
patch a condition and watch it heal itself ten minutes later.
@nadaverell
nadaverell requested a review from hisco as a code owner August 10, 2026 00:43

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c3c25f7. Configure here.

Comment thread scripts/kyverno-demo.sh
Comment thread scripts/kyverno-demo.sh
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