@@ -57,38 +57,56 @@ func setInstalledStatusFromRevisionStates(ext *ocv1.ClusterExtension, revisionSt
5757 // Nothing is installed
5858 if revisionStates .Installed == nil {
5959 setInstallStatus (ext , nil )
60- reason := determineFailureReason (revisionStates .RollingOut )
60+ reason := determineInstalledReason (revisionStates .RollingOut )
6161 setInstalledStatusConditionFalse (ext , reason , "No bundle installed" )
6262 return
6363 }
64- // Something is installed
64+
65+ // Something is installed - check if upgrade is in progress
6566 installStatus := & ocv1.ClusterExtensionInstallStatus {
6667 Bundle : revisionStates .Installed .BundleMetadata ,
6768 }
6869 setInstallStatus (ext , installStatus )
70+
71+ if len (revisionStates .RollingOut ) > 0 {
72+ latestRevision := revisionStates .RollingOut [len (revisionStates .RollingOut )- 1 ]
73+ progressingCond := apimeta .FindStatusCondition (latestRevision .Conditions , ocv1 .ClusterExtensionRevisionTypeProgressing )
74+
75+ if progressingCond != nil && progressingCond .Reason == string (ocv1 .ReasonRollingOut ) {
76+ setInstalledStatusConditionUpgrading (ext , fmt .Sprintf ("Upgrading from %s" , revisionStates .Installed .Image ))
77+ return
78+ }
79+ }
80+
6981 setInstalledStatusConditionSuccess (ext , fmt .Sprintf ("Installed bundle %s successfully" , revisionStates .Installed .Image ))
7082}
7183
72- // determineFailureReason determines the appropriate reason for the Installed condition
84+ // determineInstalledReason determines the appropriate reason for the Installed condition
7385// when no bundle is installed (Installed: False).
7486//
7587// Returns Failed when:
7688// - No rolling revisions exist (nothing to install)
7789// - The latest rolling revision has Reason: Retrying (indicates an error occurred)
7890//
91+ // Returns Installing when:
92+ // - The latest rolling revision explicitly has Reason: RollingOut (healthy installation in progress)
93+ //
7994// Returns Absent when:
80- // - Rolling revisions exist with the latest having Reason: RollingOut (healthy phased rollout in progress )
95+ // - Rolling revisions exist but have no conditions set ( rollout just started )
8196//
8297// Rationale:
8398// - Failed: Semantically indicates an error prevented installation
84- // - Absent: Semantically indicates "not there yet" (neutral state, e.g., during healthy rollout)
99+ // - Installing: Semantically indicates a first-time installation is actively in progress
100+ // - Absent: Neutral state when rollout exists but hasn't progressed enough to determine health
85101// - Retrying reason indicates an error (config validation, apply failure, etc.)
86- // - RollingOut reason indicates healthy progress (not an error)
102+ // - RollingOut reason indicates confirmed healthy progress
87103// - Only the LATEST revision matters - old errors superseded by newer healthy revisions should not cause Failed
88104//
105+ // Note: This function is only called when Installed == nil (first-time installation scenario).
89106// Note: rollingRevisions are sorted in ascending order by Spec.Revision (oldest to newest),
90- // so the latest revision is the LAST element in the array.
91- func determineFailureReason (rollingRevisions []* RevisionMetadata ) string {
107+ //
108+ // so the latest revision is the LAST element in the array.
109+ func determineInstalledReason (rollingRevisions []* RevisionMetadata ) string {
92110 if len (rollingRevisions ) == 0 {
93111 return ocv1 .ReasonFailed
94112 }
@@ -97,14 +115,21 @@ func determineFailureReason(rollingRevisions []*RevisionMetadata) string {
97115 // Latest revision is the last element in the array (sorted ascending by Spec.Revision)
98116 latestRevision := rollingRevisions [len (rollingRevisions )- 1 ]
99117 progressingCond := apimeta .FindStatusCondition (latestRevision .Conditions , ocv1 .ClusterExtensionRevisionTypeProgressing )
100- if progressingCond != nil && progressingCond .Reason == string (ocv1 .ClusterExtensionRevisionReasonRetrying ) {
101- // Retrying indicates an error occurred (config, apply, validation, etc.)
102- // Use Failed for semantic correctness: installation failed due to error
103- return ocv1 .ReasonFailed
118+ if progressingCond != nil {
119+ if progressingCond .Reason == string (ocv1 .ClusterExtensionRevisionReasonRetrying ) {
120+ // Retrying indicates an error occurred (config, apply, validation, etc.)
121+ // Use Failed for semantic correctness: installation failed due to error
122+ return ocv1 .ReasonFailed
123+ }
124+ if progressingCond .Reason == string (ocv1 .ReasonRollingOut ) {
125+ // RollingOut indicates healthy progress is confirmed
126+ // Use Installing to communicate that a first-time installation is actively in progress
127+ return ocv1 .ReasonInstalling
128+ }
104129 }
105130
106- // No error detected in latest revision - it's progressing healthily (RollingOut) or no conditions set
107- // Use Absent for neutral "not installed yet" state
131+ // No progressing condition or unknown reason - rollout just started or hasn't progressed
132+ // Use Absent as neutral state
108133 return ocv1 .ReasonAbsent
109134}
110135
@@ -119,6 +144,17 @@ func setInstalledStatusConditionSuccess(ext *ocv1.ClusterExtension, message stri
119144 })
120145}
121146
147+ // setInstalledStatusConditionUpgrading sets the installed status condition to upgrading.
148+ func setInstalledStatusConditionUpgrading (ext * ocv1.ClusterExtension , message string ) {
149+ SetStatusCondition (& ext .Status .Conditions , metav1.Condition {
150+ Type : ocv1 .TypeInstalled ,
151+ Status : metav1 .ConditionTrue ,
152+ Reason : ocv1 .ReasonUpgrading ,
153+ Message : message ,
154+ ObservedGeneration : ext .GetGeneration (),
155+ })
156+ }
157+
122158// setInstalledStatusConditionFailed sets the installed status condition to failed.
123159func setInstalledStatusConditionFalse (ext * ocv1.ClusterExtension , reason string , message string ) {
124160 SetStatusCondition (& ext .Status .Conditions , metav1.Condition {
0 commit comments