@@ -1008,13 +1008,94 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
10081008 c .mu .Lock ()
10091009 defer c .mu .Unlock ()
10101010
1011+ // Block all spec changes when cluster is stopped or stopping
1012+ if c .Status .Stopped () || c .Status .Stopping () {
1013+ lifecyclePhase := ""
1014+ if newSpec .Spec .Lifecycle != nil {
1015+ lifecyclePhase = newSpec .Spec .Lifecycle .Phase
1016+ }
1017+ // During Stopping: block ALL spec changes (no cancellation allowed)
1018+ // During Stopped: only block if keeping lifecycle.phase="stopped"
1019+ if c .Status .Stopping () {
1020+ return fmt .Errorf ("cannot update cluster while it is stopping. Wait for it to fully stop first" )
1021+ }
1022+ if lifecyclePhase == "stopped" {
1023+ return fmt .Errorf ("cannot update cluster while stopped. Remove lifecycle.phase to wake up the cluster" )
1024+ }
1025+ }
1026+
10111027 newSpec .Status .PostgresClusterStatus = acidv1 .ClusterStatusUpdating
10121028
10131029 newSpec , err := c .KubeClient .SetPostgresCRDStatus (c .clusterName (), newSpec )
10141030 if err != nil {
10151031 return fmt .Errorf ("could not set cluster status to updating: %w" , err )
10161032 }
10171033
1034+ // Check if user is initiating hibernate (Running -> Stopping)
1035+ if c .Status .Running () && newSpec .Spec .Lifecycle != nil && newSpec .Spec .Lifecycle .Phase == "stopped" {
1036+ c .logger .Infof ("[lifecycle] initiating hibernate for cluster %s: current numberOfInstances=%d" , c .Name , c .Spec .NumberOfInstances )
1037+
1038+ // Store previousNumberOfInstances BEFORE setting numberOfInstances to 0
1039+ newSpec .Status .PreviousNumberOfInstances = c .Spec .NumberOfInstances
1040+ newSpec .Spec .NumberOfInstances = 0
1041+ newSpec .Status .PostgresClusterStatus = acidv1 .ClusterStatusStopping
1042+
1043+ c .logger .Infof ("[lifecycle] hibernate initiated: setting numberOfInstances=0, previousNumberOfInstances=%d" , newSpec .Status .PreviousNumberOfInstances )
1044+
1045+ // Update spec first (Update only updates spec when CR has status subresource)
1046+ pgUpdated , err := c .KubeClient .UpdatePostgresCR (c .clusterName (), newSpec )
1047+ if err != nil {
1048+ return fmt .Errorf ("could not update spec during hibernate: %w" , err )
1049+ }
1050+ c .logger .Infof ("[lifecycle] hibernate: spec updated successfully" )
1051+
1052+ // Update status separately - we need to preserve the status values we set
1053+ // because UpdatePostgresCR returns object with status zeroed (subresource behavior)
1054+ pgUpdated .Status .PreviousNumberOfInstances = newSpec .Status .PreviousNumberOfInstances
1055+ pgUpdated .Status .PostgresClusterStatus = newSpec .Status .PostgresClusterStatus
1056+
1057+ pgUpdated , err = c .KubeClient .SetPostgresCRDStatus (c .clusterName (), pgUpdated )
1058+ if err != nil {
1059+ return fmt .Errorf ("could not update status during hibernate: %w" , err )
1060+ }
1061+ c .logger .Infof ("[lifecycle] hibernate: status updated successfully, previousNumberOfInstances=%d" , pgUpdated .Status .PreviousNumberOfInstances )
1062+
1063+ c .setSpec (pgUpdated )
1064+ return nil
1065+ }
1066+
1067+ // Check if user is waking up from stopped state (Stopped -> Running)
1068+ // This is when user clears lifecycle.phase to wake up the cluster
1069+ if c .Status .Stopped () && (newSpec .Spec .Lifecycle == nil || newSpec .Spec .Lifecycle .Phase != "stopped" ) {
1070+ if newSpec .Status .PreviousNumberOfInstances > 0 {
1071+ c .logger .Infof ("[lifecycle] waking up cluster %s: restoring numberOfInstances=%d" , c .Name , newSpec .Status .PreviousNumberOfInstances )
1072+
1073+ // Restore numberOfInstances from previousNumberOfInstances
1074+ newSpec .Spec .NumberOfInstances = newSpec .Status .PreviousNumberOfInstances
1075+ newSpec .Status .PostgresClusterStatus = acidv1 .ClusterStatusUpdating
1076+
1077+ // Update spec first
1078+ pgUpdated , err := c .KubeClient .UpdatePostgresCR (c .clusterName (), newSpec )
1079+ if err != nil {
1080+ return fmt .Errorf ("could not update spec during wake-up: %w" , err )
1081+ }
1082+ c .logger .Infof ("[lifecycle] wake-up: spec updated successfully" )
1083+
1084+ // Update status separately, and clear previousNumberOfInstances after restore
1085+ pgUpdated .Status .PreviousNumberOfInstances = 0 // Clear after successful restore
1086+ pgUpdated .Status .PostgresClusterStatus = newSpec .Status .PostgresClusterStatus
1087+
1088+ pgUpdated , err = c .KubeClient .SetPostgresCRDStatus (c .clusterName (), pgUpdated )
1089+ if err != nil {
1090+ return fmt .Errorf ("could not update status during wake-up: %w" , err )
1091+ }
1092+ c .logger .Infof ("[lifecycle] wake-up: status updated successfully, previousNumberOfInstances cleared" )
1093+
1094+ c .setSpec (pgUpdated )
1095+ return nil
1096+ }
1097+ }
1098+
10181099 if ! c .isInMaintenanceWindow (newSpec .Spec .MaintenanceWindows ) {
10191100 // do not apply any major version related changes yet
10201101 newSpec .Spec .PostgresqlParam .PgVersion = oldSpec .Spec .PostgresqlParam .PgVersion
0 commit comments