feat(provider): support CloudNativePG Cluster hibernation in the Kubernetes provider#944
Conversation
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
There was a problem hiding this comment.
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.Interfaceinto the KubernetesProvider(constructor signature change), instantiated insabliercmd/provider.goand the shared test harness. - Add a new
cnpgclusterworkload kind withinspect/hibernate/list/eventsimplementations, routed frominstance_{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
left a comment
There was a problem hiding this comment.
LGTM!
Thank you for this feature, I hope this comes handy to you :)
Closes #943
What this does
Following up on the discussion in #943, this adds a new
cnpgclusterworkload kind to the Kubernetes provider so Sablier can start and stop CloudNativePGClusterresources, right next to Deployments and StatefulSets.Instead of scaling a replica count, Sablier toggles CloudNativePG's declarative hibernation annotation:
cnpg.io/hibernation: "on"(operator scales the cluster down and removes its workload, keeps the PVCs)cnpg.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
k8s.io/client-go/dynamic(already pulled by client-go) rather than the typed CloudNativePG client, to keep the dependency footprint minimal — nocontroller-runtime. The dynamic client is wired into the provider next to the typed clientset.sablier.Providerinterface, no new CLI flag. The new kind is routed inside the existinginstance_{start,stop,inspect,list,events}.go.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 indocs/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:
cnpgcluster_test.go) usingclient-go/dynamic/fake: status mapping (ready/starting/stopped), hibernate patching viaInstanceStart/InstanceStop, and list/groups label filtering.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).Clusterand awhoamiapp sharing onesablier.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 fullpkg/provider/kubernetestest suite are green.Happy to adjust anything — naming of the kind, readiness semantics for multi-instance clusters, etc.