Skip to content

feat: NebariApp spec.database provisions managed PostgreSQL via CloudNativePG#151

Open
tylerpotts wants to merge 7 commits into
mainfrom
feat/nebariapp-database
Open

feat: NebariApp spec.database provisions managed PostgreSQL via CloudNativePG#151
tylerpotts wants to merge 7 commits into
mainfrom
feat/nebariapp-database

Conversation

@tylerpotts

@tylerpotts tylerpotts commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the NebariApp side of nebari-dev/nebari-infrastructure-core#303: packs request a managed PostgreSQL database through the CRD, and the operator provisions it on CloudNativePG.

spec:
  database:
    enabled: true
    provider: cloudnativepg   # default and only supported value
    instances: 1              # default 1, max 9 (platform guardrail)
    size: 1Gi                 # default; cannot be decreased (CNPG restriction)

When enabled, the operator:

  1. Validates CNPG's 50-char Cluster-name cap up front (friendly preflight error instead of a buried webhook rejection)
  2. Creates/updates a CNPG Cluster named <name>-db (owner-referenced to the NebariApp) and polls readiness
  3. Verifies CNPG's generated <name>-db-app Secret is owned by our Cluster, then normalizes it into <name>-db-credentials with keys host, port, username, password, database, uri
  4. Scopes get on that Secret to the app's ServiceAccount via Role/RoleBinding
  5. Reports through a DatabaseReady condition (DatabaseProvisioning / Available / CNPGNotInstalled / DatabaseDisabled / Failed) and status.databaseSecretRef

Design contracts (deliberate)

  • A config toggle can never destroy a database. enabled: false stops management and reports DatabaseDisabled; the Cluster, data, and credentials remain. Only deleting the NebariApp cascades (standard owner-reference GC). Operator RBAC has no delete verb on postgresql.cnpg.io/clusters.
  • CNPG is optional infrastructure. No startup watch on CNPG types (a missing CRD must not break the manager); readiness is polled, and a missing CRD degrades to DatabaseReady=False/CNPGNotInstalled while the rest of the app reconciles normally.
  • Credential freshness: a Secrets watch maps CNPG's <app>-db-app secrets back to their NebariApp, so password rotations propagate without waiting for the periodic requeue.
  • Typed CNPG objects via the types-only github.com/cloudnative-pg/api v1.28.0 (the newest tag whose transitive deps stay on k8s.io 0.34; v1.28.1+/v1.29+ force 0.35+; rationale recorded in go.mod).

Cross-repo sequencing

The CloudNativePG operator itself is installed by NIC as unconditional foundational infrastructure in nebari-dev/nebari-infrastructure-core#455 (open; originally an opt-in toggle, reworked per review). Sequencing is naturally safe: this feature only reaches clusters once NIC bumps its pinned operator version, which happens after PR 455 merges.

Test Plan

  • make test green: database package 88.1% coverage (17 tests: provisioning, defaults, readiness poll, credentials normalization incl. missing-key and foreign-owner rejection, RBAC incl. custom ServiceAccount, disable-keeps-everything, foreign-cluster status cleanup, CNPG-absent degrade, name-cap preflight, steady-state idempotency without duplicate events); naming 96.6% (incl. the watch's name-mapping and the 50-char/253-char validators)
  • make lint, go vet ./..., gofmt clean; make manifests generate docs produces no drift (CI staleness checks pass)
  • CRD validation verified against a live kube-apiserver 1.34 during review: defaults, instances 0/10 rejected, size zero-quantity rejected via CEL, enum/pattern rejections
  • End-to-end on a cluster with CNPG installed (via NIC PR 455): apply a NebariApp with database.enabled: true, wait for DatabaseReady=Available, psql via the <name>-db-credentials secret

Notes for reviewers

  • CRD installation now requires Kubernetes >= 1.29 (the size check uses CEL's quantity() library); documented in docs/reconcilers/database.md.
  • The internal/controller envtest suite has a pre-existing failure on main (HTTPRoute scheme registration), reproducible at the base commit; it is excluded from make test by the Makefile. A controller-level database happy-path case is a follow-up once that suite is fixed.
  • The docs "Code Organization" tree and entry-point snippet in docs/reconcilers/README.md were already stale before this branch (missing tls/ too); left for a separate docs cleanup.

Managed PostgreSQL request (provider cloudnativepg, instances, size),
DatabaseReady condition, databaseSecretRef status field, and event
reasons. The reconciler lands separately.

Part of nebari-dev/nebari-infrastructure-core#303
Provisions a CloudNativePG Cluster per NebariApp with spec.database
enabled, polls readiness (no informer: the CNPG CRDs are optional
infrastructure and a watch on a missing CRD breaks manager startup),
normalizes CNPG's generated app secret into <name>-db-credentials
(host, port, username, password, database, uri), and scopes read
access to the app's ServiceAccount. Validates the 50-char CNPG
cluster-name cap up front. Missing CNPG degrades to a
DatabaseReady=False/CNPGNotInstalled condition. Disabling never
deletes the database; deleting the NebariApp cascades via owner
references.

Pinned to cloudnative-pg/api v1.28.0, the newest types-only release
whose transitive deps stay on k8s.io 0.34 (v1.28.1+ forces 0.35+);
go mod tidy incidentally bumps ginkgo/gomega test deps.

Part of nebari-dev/nebari-infrastructure-core#303
Invoked after auth with the TLS-style poll-and-return pattern; scheme
registration for CNPG types (client-side, safe when the CRD is
absent); a Secrets watch mapping CNPG app secrets back to their
NebariApp so password rotations refresh the normalized credentials
copy; operator RBAC for postgresql.cnpg.io clusters without the
delete verb.

Part of nebari-dev/nebari-infrastructure-core#303
Verify the CNPG connection secret is owned by our Cluster before
republishing it as app credentials; extract the db-app secret name
mapping behind naming.NebariAppNameForDatabaseSecret with tests (the
Secrets watch depends on it); document the CEL quantity() Kubernetes
1.29 floor and the instances guardrail; correct the immutable-name
guidance; normalize the nolint directive.

Part of nebari-dev/nebari-infrastructure-core#303
@github-actions

Copy link
Copy Markdown

Docker Images Built

Images pushed to Quay.io for branch feat-nebariapp-database:

Image Tag Platforms
Operator quay.io/nebari/nebari-operator:feat-nebariapp-database linux/amd64 + linux/arm64

Test the operator:

kubectl apply -k https://github.com/nebari-dev/nebari-operator.git/config/default?ref=feat/nebariapp-database
kubectl set image deployment/nebari-operator-controller-manager manager=quay.io/nebari/nebari-operator:feat-nebariapp-database -n nebari-operator-system

…ggle

NIC PR 455 was reworked per review to install the CloudNativePG operator
as unconditional foundational infrastructure; the database.enabled config
toggle no longer exists. Update the field doc, the CNPGNotInstalled
condition message, its test, and the reconciler docs page accordingly.
Regenerated CRD bases and api-reference.
@github-actions

Copy link
Copy Markdown

Docker Images Built

Images pushed to Quay.io for branch feat-nebariapp-database:

Image Tag Platforms
Operator quay.io/nebari/nebari-operator:feat-nebariapp-database linux/amd64 + linux/arm64

Test the operator:

kubectl apply -k https://github.com/nebari-dev/nebari-operator.git/config/default?ref=feat/nebariapp-database
kubectl set image deployment/nebari-operator-controller-manager manager=quay.io/nebari/nebari-operator:feat-nebariapp-database -n nebari-operator-system

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.

2 participants