unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
api-designconfiguration-managementtesting-strategy

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.