-
Notifications
You must be signed in to change notification settings - Fork 71
🐛 fix: Configuration validation errors now show "Failed" status instead of "Absent" #2465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
openshift-merge-bot
merged 7 commits into
operator-framework:main
from
camilamacedo86:fix-config-errors-status
Jan 30, 2026
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
cea1d53
Make config errors permanent instead of retrying
camilamacedo86 86ba3ae
Show "Failed" status when config is wrong
camilamacedo86 5304ad7
Fix status not updating when BOXCUTTER config fails
camilamacedo86 8f42220
Add tests for config error handling
camilamacedo86 f805c05
Remove 'terminal error:' prefix from status messages
camilamacedo86 511c8c1
Add custom TerminalError with granular Reason support
camilamacedo86 c2807f5
Update internal/operator-controller/controllers/boxcutter_reconcile_s…
camilamacedo86 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ import ( | |
| "sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
|
|
||
| ocv1 "github.com/operator-framework/operator-controller/api/v1" | ||
| errorutil "github.com/operator-framework/operator-controller/internal/shared/util/error" | ||
| ) | ||
|
|
||
| const ( | ||
|
|
@@ -56,11 +57,8 @@ func setInstalledStatusFromRevisionStates(ext *ocv1.ClusterExtension, revisionSt | |
| // Nothing is installed | ||
| if revisionStates.Installed == nil { | ||
| setInstallStatus(ext, nil) | ||
| if len(revisionStates.RollingOut) == 0 { | ||
| setInstalledStatusConditionFalse(ext, ocv1.ReasonFailed, "No bundle installed") | ||
| } else { | ||
| setInstalledStatusConditionFalse(ext, ocv1.ReasonAbsent, "No bundle installed") | ||
| } | ||
| reason := determineFailureReason(revisionStates.RollingOut) | ||
| setInstalledStatusConditionFalse(ext, reason, "No bundle installed") | ||
| return | ||
| } | ||
| // Something is installed | ||
|
|
@@ -71,6 +69,45 @@ func setInstalledStatusFromRevisionStates(ext *ocv1.ClusterExtension, revisionSt | |
| setInstalledStatusConditionSuccess(ext, fmt.Sprintf("Installed bundle %s successfully", revisionStates.Installed.Image)) | ||
| } | ||
|
|
||
| // determineFailureReason determines the appropriate reason for the Installed condition | ||
| // when no bundle is installed (Installed: False). | ||
| // | ||
| // Returns Failed when: | ||
| // - No rolling revisions exist (nothing to install) | ||
| // - The latest rolling revision has Reason: Retrying (indicates an error occurred) | ||
| // | ||
| // Returns Absent when: | ||
| // - Rolling revisions exist with the latest having Reason: RollingOut (healthy phased rollout in progress) | ||
| // | ||
| // Rationale: | ||
| // - Failed: Semantically indicates an error prevented installation | ||
| // - Absent: Semantically indicates "not there yet" (neutral state, e.g., during healthy rollout) | ||
| // - Retrying reason indicates an error (config validation, apply failure, etc.) | ||
| // - RollingOut reason indicates healthy progress (not an error) | ||
| // - Only the LATEST revision matters - old errors superseded by newer healthy revisions should not cause Failed | ||
| // | ||
| // Note: rollingRevisions are sorted in ascending order by Spec.Revision (oldest to newest), | ||
| // so the latest revision is the LAST element in the array. | ||
| func determineFailureReason(rollingRevisions []*RevisionMetadata) string { | ||
| if len(rollingRevisions) == 0 { | ||
| return ocv1.ReasonFailed | ||
| } | ||
|
|
||
| // Check if the LATEST rolling revision indicates an error (Retrying reason) | ||
| // Latest revision is the last element in the array (sorted ascending by Spec.Revision) | ||
| latestRevision := rollingRevisions[len(rollingRevisions)-1] | ||
| progressingCond := apimeta.FindStatusCondition(latestRevision.Conditions, ocv1.ClusterExtensionRevisionTypeProgressing) | ||
| if progressingCond != nil && progressingCond.Reason == string(ocv1.ClusterExtensionRevisionReasonRetrying) { | ||
| // Retrying indicates an error occurred (config, apply, validation, etc.) | ||
| // Use Failed for semantic correctness: installation failed due to error | ||
| return ocv1.ReasonFailed | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @pedjak @perdasilva ^ We check the LATEST only |
||
|
|
||
| // No error detected in latest revision - it's progressing healthily (RollingOut) or no conditions set | ||
| // Use Absent for neutral "not installed yet" state | ||
| return ocv1.ReasonAbsent | ||
| } | ||
|
|
||
| // setInstalledStatusConditionSuccess sets the installed status condition to success. | ||
| func setInstalledStatusConditionSuccess(ext *ocv1.ClusterExtension, message string) { | ||
| SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ | ||
|
|
@@ -119,12 +156,20 @@ func setStatusProgressing(ext *ocv1.ClusterExtension, err error) { | |
|
|
||
| if err != nil { | ||
| progressingCond.Reason = ocv1.ReasonRetrying | ||
| progressingCond.Message = err.Error() | ||
| // Unwrap TerminalError to avoid "terminal error:" prefix in message | ||
camilamacedo86 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| progressingCond.Message = errorutil.UnwrapTerminal(err).Error() | ||
| } | ||
|
|
||
| if errors.Is(err, reconcile.TerminalError(nil)) { | ||
| progressingCond.Status = metav1.ConditionFalse | ||
| progressingCond.Reason = ocv1.ReasonBlocked | ||
| // Try to extract a specific reason from the terminal error. | ||
| // If the error was created with NewTerminalError(reason, err), use that reason. | ||
| // Otherwise, fall back to the generic "Blocked" reason. | ||
| if reason, ok := errorutil.ExtractTerminalReason(err); ok { | ||
| progressingCond.Reason = reason | ||
| } else { | ||
| progressingCond.Reason = ocv1.ReasonBlocked | ||
| } | ||
| } | ||
|
|
||
| SetStatusCondition(&ext.Status.Conditions, progressingCond) | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would set ReasonAbset always when we should say Failed if we see that we are retrying due to errors
c/c @pedjak @perdasilva ^