Skip to content

feat(scripts): cnpg-demo — a scripted CloudNativePG fixture cluster - #1396

Closed
nadaverell wants to merge 2 commits into
mainfrom
nadav/cnpg-demo-fixture
Closed

feat(scripts): cnpg-demo — a scripted CloudNativePG fixture cluster#1396
nadaverell wants to merge 2 commits into
mainfrom
nadav/cnpg-demo-fixture

Conversation

@nadaverell

@nadaverell nadaverell commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

CNPG had the problem make gitops-demo exists to solve, and no answer. RAD-318's fixtures lived in a hand-built cluster and a chat transcript; when the cluster went, so did they.

make cnpg-demo builds a kind cluster in ~6 minutes with enough state to verify badge, drawer, Issues, audit and filter in one pass.

What's in it

Four clusters, all 2/2 Ready, four different badgesHealthy, WAL Archiving Failing, Unrecoverable ×2. The uniform instance count is the design: every difference is invisible to counts, which is the class of bug this exists to catch. A reviewer should see four identical 2/2 rows and four different colours.

Plus a Pooler, a method: plugin ScheduledBackup, three Backups (completed / walArchivingFailing / an unmapped phase), and Velero + KubeBlocks CRs for the shared backups and clusters plurals. Those last two are load-bearing: with one engine installed, a guard that matches everything and a guard that matches the right API group behave identically, so the collision handling can't be verified at all.

The payload is the ordering

Anyone can helm install cloudnative-pg. These are the constraints that cost hours, and every one was found by running the script rather than reasoning about it. Each fails in a way that looks like success.

Freezing the operator does not hold a status. Every Postgres pod runs an instance manager that writes cluster status independently of the controller. status.phase survives a scaled-to-zero operator; status.conditions and status.readyInstances are rewritten from the pods within ~10 minutes. A hand-patched ContinuousArchiving=False demos fine and then heals itself.

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 pointing at an unroutable endpoint never becomes Ready. That still produces ContinuousArchiving=False, which is the convincing wrong answer: the condition looks right on a cluster that is simply broken. The success check therefore requires both the condition and 2/2 Ready — because asserting the condition alone passed on the broken version, 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 rendered Backup Failed — because of a resource attached to it, not anything wrong with it.

A Pooler must not share a name with one of its cluster's Services. CNPG creates <cluster>-rw/-ro/-r; a Pooler creates a Service of its own name. pg-healthy-rw found a Service it didn't own and 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 Velero expiry rendered Expired instead of 24d remaining within months of being written.

All of it is in scripts/cnpg-demo/README.md, which is the actual deliverable — the script is just its executable form.

Verified

Run end-to-end from an empty machine (down then up), then Radar pointed at the result:

  • Four badges exactly as documented, each on one line, no clipped headers
  • cnpgNoDeclarativeBackup: evaluated 4 / passed 1 — only the plugin-method cluster passes, which is what makes the check demonstrably plugin-aware rather than asserted to be
  • 4 CNPG issues, CNPGWALArchivingFailing critical on the healthy-looking cluster
  • Both backup engines rendering their own column sets on one cluster; KubeBlocks clusters rendering the generic drawer

make tsc and go build ./... clean. No production code touched.

Also

make cnpg-demo-live leaves the operator running — real failovers, switchovers and backup runs, at the cost of hand-written terminal phases. The frozen path is optimised for rendering verification and is the one that's proven; v2/v3 work wants the opposite.

CLAUDE.md gets a pointer in the same slot as the GitOps and Crossplane entries.


Note

Low Risk
Docs and dev/demo scripts only; no application, API, or auth changes.

Overview
Adds a make cnpg-demo workflow (alongside down, status, and cnpg-demo-live) that boots a kind cluster with pinned CloudNativePG and curated fixtures so Radar’s CNPG UI can be exercised without relying on a random kube context.

scripts/cnpg-demo.sh orchestrates install, waits, then applies ordering-sensitive steps: induce a real WAL archiving failure on a 2/2 Ready cluster (not a status patch), scale the operator to zero and remove webhooks so terminal phases and backup statuses stick, defer CNPG Backup creation until after the freeze, and use relative timestamps for Velero status. Subcommands cover frozen vs live operator, refreeze/thaw, and inventory.

YAML fixtures under scripts/cnpg-demo/ define four Postgres clusters (same Ready counts, different badges), Pooler, plugin-method ScheduledBackup, backup phase variants, and minimal Velero/KubeBlocks CRDs plus instances to exercise shared backups / clusters plural handling.

scripts/cnpg-demo/README.md documents the coverage matrix and the non-obvious constraints; CLAUDE.md points visual testers at this cluster the same way as GitOps/Crossplane demos.

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

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 9, 2026 08:49

@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 0eda339. Configure here.

Comment thread scripts/cnpg-demo.sh
date -u -v"${spec}" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| date -u -d "${spec/#+/} ${spec:0:1}" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| date -u +%Y-%m-%dT%H:%M:%SZ
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

GNU date fallback produces now

Medium Severity

ts_offset builds invalid GNU date -d strings like -4H - and 25d +, so on Linux both relative forms fail and the function silently returns the current time. That stamps Velero expiration as now, so the collision row shows Expired instead of remaining time — the exact failure this helper was added to avoid.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0eda339. Configure here.

Comment thread scripts/cnpg-demo.sh

if cluster_exists; then
step "Cluster '${CLUSTER_NAME}' already exists — reusing"
thaw_operator_quiet

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-run thaws over existing Backups

High Severity

Reusing a cluster thaws the operator while CNPG Backup CRs from a prior freeze are still present. The script’s own comments note that a live operator will attempt those Backups and stamp LastBackupSucceeded=False on pg-healthy, turning the baseline badge into Backup Failed. Later re-freeze only rewrites Backup phases, not the cluster condition, so the Healthy fixture stays broken across the documented idempotent up / live / refreeze flows.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0eda339. Configure here.

@nadaverell

Copy link
Copy Markdown
Contributor Author

Superseded by #1402, which carries the CNPG demo plus hardened freeze/live lifecycle handling, truthful invariants, review fixes, and end-to-end verification. Keeping this branch intact for history.

@nadaverell nadaverell closed this Aug 10, 2026
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