Skip to content

feat(provider): support CloudNativePG Cluster hibernation in the Kubernetes provider#944

Merged
acouvreur merged 1 commit into
sablierapp:mainfrom
antoinemichea:feat/cnpg-cluster-provider
May 28, 2026
Merged

feat(provider): support CloudNativePG Cluster hibernation in the Kubernetes provider#944
acouvreur merged 1 commit into
sablierapp:mainfrom
antoinemichea:feat/cnpg-cluster-provider

Conversation

@antoinemichea

@antoinemichea antoinemichea commented May 27, 2026

Copy link
Copy Markdown
Contributor

Closes #943

What this does

Following up on the discussion in #943, this adds a new cnpgcluster workload kind to the Kubernetes provider so Sablier can start and stop CloudNativePG Cluster resources, right next to Deployments and StatefulSets.

Instead of scaling a replica count, Sablier toggles CloudNativePG's declarative hibernation annotation:

  • stopcnpg.io/hibernation: "on" (operator scales the cluster down and removes its workload, keeps the PVCs)
  • startcnpg.io/hibernation: "off" (operator resumes the cluster)

My use case: I run Opencell as a StatefulSet + a Keycloak + a CloudNativePG database. With this, I can put all three in the same sablier.group, so a single request on the Traefik ingress wakes the whole stack and inactivity hibernates all of it — database included.

Design choices

  • Dynamic client, no new dependency. I went with k8s.io/client-go/dynamic (already pulled by client-go) rather than the typed CloudNativePG client, to keep the dependency footprint minimal — no controller-runtime. The dynamic client is wired into the provider next to the typed clientset.
  • Purely additive. No change to the sablier.Provider interface, no new CLI flag. The new kind is routed inside the existing instance_{start,stop,inspect,list,events}.go.
  • Safe when CloudNativePG is absent. If the CRD isn't installed, listing no-ops (returns no clusters instead of erroring) and the events informer isn't started, so non-CNPG clusters are completely unaffected.
  • Readiness mapping: hibernation on → stopped; status.readyInstances >= spec.instances → ready; otherwise starting.

New files live alongside the existing per-kind files: cnpgcluster.go, cnpgcluster_hibernate.go, cnpgcluster_inspect.go, cnpgcluster_list.go, cnpgcluster_events.go, plus tests. Docs updated in docs/providers/kubernetes.md (new section + the optional RBAC rule).

Testing done

I went a bit further than usual on testing because it touches a database:

  • Unit tests (cnpgcluster_test.go) using client-go/dynamic/fake: status mapping (ready/starting/stopped), hibernate patching via InstanceStart/InstanceStop, and list/groups label filtering.
  • Testcontainers integration test (cnpgcluster_integration_test.go), in the same spirit as the existing k3s tests: it installs a minimal Cluster CRD into the shared k3s cluster and verifies inspect / stop / start / list against the real API server. As you mentioned in feat: support CloudNativePG Cluster (hibernate) in the Kubernetes provider #943, the CNPG install is done in the test itself (CRD only — no operator needed to validate Sablier's interactions).
  • Real cluster, provider level: drove the provider against a real CloudNativePG cluster and watched the actual operator hibernate (pods → 0, PVC kept) and resume.
  • Real cluster, full end-to-end: deployed this build in-cluster behind a Traefik instance with the Sablier plugin, with a CloudNativePG Cluster and a whoami app sharing one sablier.group. First request wakes the whole group (app scaled up + database resumed), then after the session expires both are stopped together (deployment → 0, cluster → hibernated), and a later request blocks ~25s while the database recovers, then serves. Exactly the behaviour I was after.
  • make fmt, golangci-lint run, and the full pkg/provider/kubernetes test suite are green.

Happy to adjust anything — naming of the kind, readiness semantics for multi-instance clusters, etc.

Add a new "cnpgcluster" workload kind to the Kubernetes provider so Sablier
can start and stop CloudNativePG Clusters via the declarative hibernation
annotation (cnpg.io/hibernation), alongside Deployments and StatefulSets.
This lets a database share a sablier.group with the apps depending on it.

- Wire a dynamic client into the provider for Custom Resource access
- Start/stop toggle the hibernation annotation; inspect maps spec.instances,
  status.readyInstances and the annotation to ready/starting/stopped
- List/groups discover Clusters labelled sablier.enable=true; events watch
  hibernation transitions through a dynamic informer
- Gracefully no-op when the CloudNativePG CRD is absent, so non-CNPG
  clusters are unaffected
- Tests: fake-client unit tests plus a testcontainers integration test that
  installs the Cluster CRD into the shared k3s cluster

Refs sablierapp#943
@antoinemichea antoinemichea requested a review from acouvreur as a code owner May 27, 2026 10:25
@github-actions github-actions Bot added documentation Improvements or additions to documentation provider Issue related to a provider labels May 27, 2026
@acouvreur acouvreur requested a review from Copilot May 28, 2026 00:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds CloudNativePG (CNPG) Cluster support to the Kubernetes provider so Sablier can hibernate/resume CNPG-backed databases alongside Deployments and StatefulSets. Operations are routed through the existing per-kind files and use a dynamic.Interface client to manipulate the cnpg.io/hibernation annotation, list clusters, inspect status, and watch events. When the CNPG CRD is absent, listing returns no items and the events informer is skipped, so non-CNPG clusters are unaffected.

Changes:

  • Wire a dynamic.Interface into the Kubernetes Provider (constructor signature change), instantiated in sabliercmd/provider.go and the shared test harness.
  • Add a new cnpgcluster workload kind with inspect/hibernate/list/events implementations, routed from instance_{inspect,start,stop,list,events}.go.
  • Add unit tests (dynamic fake), an integration test that installs a minimal CNPG CRD into the shared k3s container, and documentation including the optional RBAC rule.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated no comments.

Show a summary per file
File Description
pkg/sabliercmd/provider.go Creates the dynamic client and passes it into the Kubernetes provider constructor.
pkg/provider/kubernetes/kubernetes.go Adds dynamic field and new constructor parameter; nil-tolerant for non-CRD callers.
pkg/provider/kubernetes/instance_inspect.go Routes cnpgcluster kind to ClusterInspect; updates error message.
pkg/provider/kubernetes/instance_start.go / instance_stop.go Early-return to clusterHibernate for the cnpgcluster kind.
pkg/provider/kubernetes/instance_list.go Appends cluster list and groups to existing deployment/statefulset results.
pkg/provider/kubernetes/instance_events.go Starts a dynamic informer for clusters only when the CRD is present.
pkg/provider/kubernetes/cnpgcluster.go Defines KindCNPGCluster, GVR, hibernation annotation/values, and ClusterName.
pkg/provider/kubernetes/cnpgcluster_inspect.go Computes Ready/Starting/Stopped from spec.instances, status.readyInstances, and the hibernation annotation.
pkg/provider/kubernetes/cnpgcluster_hibernate.go Merge-patches the hibernation annotation to on/off.
pkg/provider/kubernetes/cnpgcluster_list.go Lists clusters with sablier.enable=true; silently no-ops if CRD missing.
pkg/provider/kubernetes/cnpgcluster_events.go Dynamic informer emitting Created/Started/Stopped/Removed events on hibernation transitions.
pkg/provider/kubernetes/cnpgcluster_test.go Unit tests using the dynamic fake client.
pkg/provider/kubernetes/cnpgcluster_integration_test.go k3s integration test installing a minimal CNPG CRD.
pkg/provider/kubernetes/testcontainers_test.go Adds dynamic client and rest config to the shared test container.
pkg/provider/kubernetes/{deployment,statefulset}inspect_test.go, instance{start,stop,list,events,inspect}_test.go Update existing tests to pass the new dynamic parameter to kubernetes.New.
docs/providers/kubernetes.md Documents CNPG cluster opt-in labels, readiness semantics, and optional RBAC rule.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@acouvreur acouvreur left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

Thank you for this feature, I hope this comes handy to you :)

@acouvreur acouvreur merged commit 199f12e into sablierapp:main May 28, 2026
3 of 4 checks passed
@antoinemichea antoinemichea deleted the feat/cnpg-cluster-provider branch May 28, 2026 06:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation provider Issue related to a provider

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support CloudNativePG Cluster (hibernate) in the Kubernetes provider

4 participants