feat(actions): add hibernate/rehydrate actions for CNPG Cluster
Project: argoproj/argo-cd | Language: Go, Lua, YAML | Concepts: api-design, configuration-management, testing-strategy | Difficulty: junior
Link: https://github.com/argoproj/argo-cd/pull/28478
The problem
CloudNativePG clusters support a "hibernation" mode that stops Pods but keeps data (PVCs). Argo CD previously had no built-in resource actions to trigger this state or to bring the cluster back ("rehydrate"). Existing actions only handled reconciliation suspension, not hibernation. This made it impossible to declaratively hibernate or rehydrate a CNPG cluster using Argo CD's UI or API.
Why it's non-trivial
A naive fix might confuse hibernation with reconciliation suspension, or fail to properly surface the correct action in the UI based on the cluster's current state. It's also easy to overlook test coverage and documentation for new actions, or to forget to make the actions idempotent and safe for clusters with or without existing annotations.
The approach
1. Add new actions to the built-in documentation list
The documentation is updated to include the new actions:
+- [postgresql.cnpg.io/Cluster/hibernate](https://github.com/argoproj/argo-cd/blob/master/resource_customizations/postgresql.cnpg.io/Cluster/actions/hibernate/action.lua)
+- [postgresql.cnpg.io/Cluster/rehydrate](https://github.com/argoproj/argo-cd/blob/master/resource_customizations/postgresql.cnpg.io/Cluster/actions/rehydrate/action.lua)
This ensures users can discover and understand the new actions.
2. Add test cases for hibernate and rehydrate actions
Test coverage is extended for both new actions:
+ - action: hibernate
+ inputPath: testdata/cluster_healthy.yaml
+ expectedOutputPath: testdata/cluster_hibernated.yaml
+ - action: rehydrate
+ inputPath: testdata/cluster_hibernated.yaml
+ expectedOutputPath: testdata/cluster_rehydrated.yaml
This verifies that the actions correctly mutate the resource as intended.
3. Surface actions based on current annotation in discovery.lua
The action discovery script now checks the annotation and only surfaces the relevant action (hibernate or rehydrate):
+local isHibernated = false
+if obj.metadata and obj.metadata.annotations and obj.metadata.annotations["cnpg.io/hibernation"] == "on" then
+ isHibernated = true
+end
+
+if isHibernated then
+ actions["rehydrate"] = {
+ ["iconClass"] = "fa fa-fw fa-play",
+ ["displayName"] = "Rehydrate Cluster"
+ }
+else
+ actions["hibernate"] = {
+ ["iconClass"] = "fa fa-fw fa-pause",
+ ["displayName"] = "Hibernate Cluster"
+ }
+end
This ensures only the appropriate action is shown, preventing invalid transitions.
4. Implement the hibernate action
The hibernate action adds or updates the annotation to "on":
+if obj.metadata == nil then
+ obj.metadata = {}
+end
+
+if obj.metadata.annotations == nil then
+ obj.metadata.annotations = {}
+end
+
+obj.metadata.annotations["cnpg.io/hibernation"] = "on"
+return obj
This safely ensures the annotation is set, even if metadata or annotations were missing.
5. Implement the rehydrate action
The rehydrate action sets the annotation to "off":
+if obj.metadata == nil then
+ obj.metadata = {}
+end
+
+if obj.metadata.annotations == nil then
+ obj.metadata.annotations = {}
+end
+
+obj.metadata.annotations["cnpg.io/hibernation"] = "off"
+return obj
Again, this is robust to missing fields and idempotent.
6. Add test fixtures for hibernated and rehydrated clusters
Sample YAMLs represent the expected resource state after each action:
cluster_hibernated.yaml:
metadata:
annotations:
cnpg.io/hibernation: "on"
cluster_rehydrated.yaml:
metadata:
annotations:
cnpg.io/hibernation: "off"
These are used by the test cases to validate correct behavior.
From the review thread
No notable review discussion on this PR.
Takeaways
- Always distinguish between similar but semantically different operations in API/action design, rather than overloading a single action for multiple meanings.
- When adding new resource actions, ensure they are discoverable in documentation and only surfaced in the UI when valid for the current resource state.
- Make actions robust to missing or incomplete resource fields by initializing nested maps or lists as needed before mutation.