Skip to content

Conversation

@hongkailiu
Copy link
Member

@hongkailiu hongkailiu commented Jan 2, 2026

From this pull, CVO starts to reconcile cv.spec.desiredUpdate.acceptRisks
on a cluser if FeatureGateClusterUpdateAcceptRisks is enabled no a
non-hypershift cluster.

  • CVO does not block a conditional update if all risks that apply
    to the cluster are accpeted.

  • CVO fills up the new fields in status:

    • status.conditionalUpdateRisks,
    • status.conditionalUpdate.riskNames, and
    • status.conditionalUpdate.risks.conditions
  • CVO adds risk names into status.history.acceptedRisks if a conditional
    update is accepted.

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jan 2, 2026
@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Jan 2, 2026

@hongkailiu: This pull request references OTA-1546 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai
Copy link

coderabbitai bot commented Jan 2, 2026

Walkthrough

Adds support for accepting update risks: feature-gate wiring, operator gating, propagation of accepted-risk state into available-updates and status, per-risk condition tracking, signature and constant updates, test coverage, and E2E/utilities for accept-risks behavior. (50 words)

Changes

Cohort / File(s) Summary
Available-updates risk wiring
pkg/cvo/availableupdates.go
Added fields to availableUpdates: ShouldReconcileAcceptRisks func() bool, AcceptRisks sets.Set[string], RiskConditions map[string][]metav1.Condition; propagate via getAvailableUpdates/syncAvailableUpdates; expanded evaluateConditionalUpdate signature and logic to honor accepted risks and record per-risk conditions; added riskConditionReasonEvaluationFailed and new recommended-reason path.
Operator gating
pkg/cvo/cvo.go
Added func (optr *Operator) shouldReconcileAcceptRisks() bool to gate risk reconciliation based on feature gates and hypershift.
Status and condition propagation
pkg/cvo/status.go
Replaced exported recommended-constant usage with internal.ConditionalUpdateConditionTypeRecommended; extended updateClusterVersionStatus and callers to accept a shouldReconcileAcceptRisks parameter; added risk-aware flows to populate RiskNames, Risks and per-risk conditions.
Feature-gates model
pkg/featuregates/featuregates.go
Added acceptRisks bool to CvoGates, AcceptRisks() bool to the interface and implementation; default set to false and toggled via FeatureGateClusterUpdateAcceptRisks in enable/disable paths.
Internal constants
pkg/internal/constants.go
Added ConditionalUpdateConditionTypeRecommended and ConditionalUpdateRiskConditionTypeApplies constants to internal.
Metrics / payload / precondition updates
pkg/cvo/metrics.go, pkg/payload/precondition/clusterversion/recommendedupdate.go, .openshift-tests-extension/...json
Switched metrics/precondition tests to use internal.ConditionalUpdateConditionTypeRecommended; updated precondition check to reference the internal constant; added two payload test entries describing accept-risks behavior.
Tests and test utilities
pkg/cvo/*_test.go, test/cvo/accept_risks.go, test/util/util.go
Updated unit tests to pass and assert AcceptRisks, ShouldReconcileAcceptRisks, and RiskConditions; adjusted test signatures and comparisons; added E2E test/cvo/accept_risks.go to validate storing accepted risks and status population; added test util helpers for environment detection (TechPreviewNoUpgrade, Hypershift, MicroShift).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jan 2, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: hongkailiu

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jan 2, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/cvo/availableupdates.go (1)

521-579: Fix the risk condition storage logic to accumulate all conditions instead of silently dropping duplicates.

The code at lines 570–571 only stores a risk condition if the risk name hasn't been seen before, causing silent data loss when multiple ConditionalUpdates contain risks with the same name. Since evaluateConditionalUpdate is called once per ConditionalUpdate with a shared riskConditions map (line 444), conditions for the same risk name across different updates are overwritten or ignored.

Additionally, the TestEvaluateConditionalUpdate test calls evaluateConditionalUpdate with 3 arguments, but the function signature requires 6 (acceptRisks, shouldReconcileAcceptRisks, riskConditions). The test needs to be updated to match the current function signature and verify the riskConditions map is populated correctly.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between d245c43 and eb30e14.

⛔ Files ignored due to path filters (35)
  • go.sum is excluded by !**/*.sum
  • vendor/github.com/openshift/api/config/v1/register.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/types_cluster_version.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/types_feature.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/types_image_policy.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/types_insights.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/types_scheduling.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_00_cluster-version-operator_01_clusterversions-CustomNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_00_cluster-version-operator_01_clusterversions-Default.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_00_cluster-version-operator_01_clusterversions-DevPreviewNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_00_cluster-version-operator_01_clusterversions-OKD.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_00_cluster-version-operator_01_clusterversions-TechPreviewNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_apiservers-OKD.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_clusterimagepolicies-Default.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_clusterimagepolicies-DevPreviewNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_clusterimagepolicies-TechPreviewNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_clusterimagepolicies.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_featuregates.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_imagepolicies-Default.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_imagepolicies-DevPreviewNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_imagepolicies-TechPreviewNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_imagepolicies.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_infrastructures-OKD.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_insightsdatagathers-CustomNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_insightsdatagathers-DevPreviewNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_insightsdatagathers-TechPreviewNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_nodes-OKD.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_schedulers-SelfManagedHA-DevPreviewNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_schedulers-SelfManagedHA-OKD.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_schedulers-SelfManagedHA-TechPreviewNoUpgrade.crd.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.featuregated-crd-manifests.yaml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.swagger_doc_generated.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/api/features/features.go is excluded by !vendor/**, !**/vendor/**
  • vendor/modules.txt is excluded by !vendor/**, !**/vendor/**
📒 Files selected for processing (7)
  • go.mod
  • pkg/cvo/availableupdates.go
  • pkg/cvo/cvo.go
  • pkg/cvo/status.go
  • pkg/featuregates/featuregates.go
  • pkg/internal/constants.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.

Files:

  • pkg/payload/precondition/clusterversion/recommendedupdate.go
  • pkg/internal/constants.go
  • pkg/cvo/cvo.go
  • pkg/featuregates/featuregates.go
  • go.mod
  • pkg/cvo/status.go
  • pkg/cvo/availableupdates.go
🧬 Code graph analysis (3)
pkg/payload/precondition/clusterversion/recommendedupdate.go (1)
pkg/internal/constants.go (1)
  • ConditionalUpdateConditionTypeRecommended (76-76)
pkg/cvo/status.go (2)
pkg/internal/constants.go (1)
  • ConditionalUpdateConditionTypeRecommended (76-76)
pkg/featuregates/featuregates.go (1)
  • CvoGateChecker (19-41)
pkg/cvo/availableupdates.go (2)
pkg/clusterconditions/clusterconditions.go (2)
  • Condition (19-29)
  • ConditionRegistry (31-42)
pkg/internal/constants.go (2)
  • ConditionalUpdateConditionTypeRecommended (76-76)
  • ConditionalUpdateRiskConditionTypeApplies (80-80)
🔇 Additional comments (10)
pkg/payload/precondition/clusterversion/recommendedupdate.go (1)

14-15: LGTM!

Good refactor to use the centralized constant internal.ConditionalUpdateConditionTypeRecommended instead of a hardcoded string. This improves maintainability and consistency across the codebase.

Also applies to: 55-55

pkg/cvo/cvo.go (1)

1094-1102: LGTM!

The new shouldReconcileAcceptRisks() method follows the same pattern as shouldReconcileCVOConfiguration() and correctly gates the functionality behind the AcceptRisks feature gate while excluding HyperShift environments.

pkg/cvo/availableupdates.go (1)

177-207: LGTM on struct additions.

The new fields ShouldReconcileAcceptRisks, AcceptRisks, and RiskConditions are well-placed in the struct and align with the risk acceptance feature requirements.

pkg/featuregates/featuregates.go (2)

38-41: LGTM!

The AcceptRisks feature gate is properly added following the established pattern for other CVO feature gates (StatusReleaseArchitecture, CVOConfiguration).

Also applies to: 55-56, 70-72


103-104: Verify FeatureGateClusterUpdateAcceptRisks is exported by the features package.

The code references features.FeatureGateClusterUpdateAcceptRisks imported from github.com/openshift/api/features. While this constant is not defined locally and cannot be verified within this repository, the go.mod uses a forked version (hongkailiu/api). Ensure this constant exists in the forked API package and will remain available when migrating to the official openshift/api package.

Also applies to: 113-114

pkg/cvo/status.go (3)

40-41: LGTM!

Good refactor to use the centralized constant for condition type lookup.


191-199: LGTM on signature change.

The extended signature properly accepts the shouldReconcileAcceptRisks callback to gate the risk reconciliation behavior.


452-465: LGTM on conditionalUpdateRisks helper.

The function correctly deduplicates risks by name and returns a flat list of unique ConditionalUpdateRisk entries.

pkg/internal/constants.go (1)

73-81: LGTM!

Well-documented constants that centralize condition type identifiers. Both constants are appropriately named, follow existing patterns in this file, and are actively used throughout the codebase.

go.mod (1)

97-97: Personal fork replacement should not be merged.

This replace directive points to a personal fork (github.com/hongkailiu/api) instead of the official github.com/openshift/api. Per the commit message "to-be-replaced by api#2360", this should be updated to the official repository once the upstream PR is merged before this change can be merged to main.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
pkg/cvo/status.go (1)

425-454: Add nil check for getAvailableUpdates() at line 428.

Line 428 directly accesses getAvailableUpdates().RiskConditions, but the function signature indicates getAvailableUpdates can return nil. This would cause a panic. The mergeReleaseMetadata function at line 227 safely checks for nil (cvo.go:909), but conditionalUpdateWithRiskNamesAndRiskConditions does not.

Suggested fix
 func conditionalUpdateWithRiskNamesAndRiskConditions(conditionalUpdates []configv1.ConditionalUpdate, getAvailableUpdates func() *availableUpdates, desiredImage string) ([]configv1.ConditionalUpdate, []string) {
 	var result []configv1.ConditionalUpdate
 	var riskNamesForDesiredImage []string
-	riskConditions := getAvailableUpdates().RiskConditions
+	var riskConditions map[string][]metav1.Condition
+	if au := getAvailableUpdates(); au != nil {
+		riskConditions = au.RiskConditions
+	}
 	for _, conditionalUpdate := range conditionalUpdates {
🧹 Nitpick comments (1)
pkg/cvo/status.go (1)

429-454: Review risk condition synchronization logic.

Lines 433-450 implement risk condition synchronization with the following flow:

  1. Build a set of risk names from conditionalUpdate.Risks
  2. Remove stale conditions (not in fetched riskConditions)
  3. Merge in new conditions from riskConditions

However, there's a subtle logic concern at lines 436-445:

for _, condition := range risk.Conditions {
    if found := meta.FindStatusCondition(conditions, condition.Type); found == nil {
        riskTypesToRemove.Insert(condition.Type)
    }
}

This removes condition types from risk.Conditions if they're not found in riskConditions[risk.Name]. Then at lines 446-448:

for _, condition := range conditions {
    meta.SetStatusCondition(&risk.Conditions, condition)
}

New conditions are added. This is correct if the intent is to replace conditions on each risk with only those from riskConditions. Please confirm this is the intended behavior.

💡 Consider adding clarifying comments

The synchronization logic would benefit from comments explaining the intent:

 		riskNames := sets.New[string]()
 		for _, risk := range conditionalUpdate.Risks {
 			riskNames.Insert(risk.Name)
+			// Remove stale condition types that are no longer present in the fetched risk conditions
 			riskTypesToRemove := sets.New[string]()
 			conditions := riskConditions[risk.Name]
 			for _, condition := range risk.Conditions {
 				if found := meta.FindStatusCondition(conditions, condition.Type); found == nil {
 					riskTypesToRemove.Insert(condition.Type)
 				}
 			}
 			for riskTypeToRemove := range riskTypesToRemove {
 				meta.RemoveStatusCondition(&risk.Conditions, riskTypeToRemove)
 			}
+			// Merge in current conditions from the risk conditions map
 			for _, condition := range conditions {
 				meta.SetStatusCondition(&risk.Conditions, condition)
 			}
 		}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between eb30e14 and cd8eab2.

⛔ Files ignored due to path filters (23)
  • go.sum is excluded by !**/*.sum
  • vendor/github.com/openshift/client-go/config/applyconfigurations/config/v1/custom.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/applyconfigurations/config/v1/gatherconfig.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/applyconfigurations/config/v1/gathererconfig.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/applyconfigurations/config/v1/gatherers.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/applyconfigurations/config/v1/insightsdatagather.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/applyconfigurations/config/v1/insightsdatagatherspec.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/applyconfigurations/config/v1/persistentvolumeclaimreference.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/applyconfigurations/config/v1/persistentvolumeconfig.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/applyconfigurations/config/v1/storage.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/applyconfigurations/internal/internal.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/applyconfigurations/utils.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/clientset/versioned/typed/config/v1/config_client.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/clientset/versioned/typed/config/v1/fake/fake_config_client.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/clientset/versioned/typed/config/v1/fake/fake_insightsdatagather.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/clientset/versioned/typed/config/v1/generated_expansion.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/clientset/versioned/typed/config/v1/insightsdatagather.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/informers/externalversions/config/v1/insightsdatagather.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/informers/externalversions/config/v1/interface.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/informers/externalversions/generic.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/listers/config/v1/expansion_generated.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/client-go/config/listers/config/v1/insightsdatagather.go is excluded by !vendor/**, !**/vendor/**
  • vendor/modules.txt is excluded by !vendor/**, !**/vendor/**
📒 Files selected for processing (11)
  • go.mod
  • pkg/cvo/availableupdates.go
  • pkg/cvo/availableupdates_test.go
  • pkg/cvo/cvo.go
  • pkg/cvo/metrics.go
  • pkg/cvo/metrics_test.go
  • pkg/cvo/status.go
  • pkg/cvo/status_test.go
  • pkg/featuregates/featuregates.go
  • pkg/internal/constants.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/cvo/cvo.go
  • pkg/internal/constants.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.

Files:

  • go.mod
  • pkg/cvo/metrics_test.go
  • pkg/cvo/status_test.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
  • pkg/cvo/status.go
  • pkg/cvo/metrics.go
  • pkg/featuregates/featuregates.go
  • pkg/cvo/availableupdates.go
  • pkg/cvo/availableupdates_test.go
🧬 Code graph analysis (5)
pkg/cvo/metrics_test.go (2)
pkg/cvo/status.go (1)
  • ConditionalUpdateConditionTypeRecommended (35-35)
pkg/internal/constants.go (1)
  • ConditionalUpdateConditionTypeRecommended (76-76)
pkg/payload/precondition/clusterversion/recommendedupdate.go (2)
pkg/cvo/status.go (1)
  • ConditionalUpdateConditionTypeRecommended (35-35)
pkg/internal/constants.go (1)
  • ConditionalUpdateConditionTypeRecommended (76-76)
pkg/cvo/status.go (1)
pkg/featuregates/featuregates.go (1)
  • CvoGateChecker (19-41)
pkg/cvo/metrics.go (2)
pkg/cvo/status.go (1)
  • ConditionalUpdateConditionTypeRecommended (35-35)
pkg/internal/constants.go (1)
  • ConditionalUpdateConditionTypeRecommended (76-76)
pkg/cvo/availableupdates_test.go (2)
pkg/featuregates/featuregates.go (1)
  • DefaultCvoGates (75-83)
pkg/cvo/cvo.go (1)
  • New (204-296)
🔇 Additional comments (30)
go.mod (1)

13-13: Clarify the relationship and necessity of replace directives.

Line 13 requires github.com/openshift/api at version 20251222154221, but line 97 replaces it with a fork at version 20251223025917. Line 99 replaces openshift/client-go with itself at a different version.

Provide inline comments explaining:

  • Why the fork in line 97 is necessary instead of using the upstream version pinned in line 13.
  • Why line 99 replaces the module with itself but at a different timestamp, and whether this indicates a transitive dependency resolution issue that should be addressed differently.
  • Whether these replaces are expected to be temporary or permanent, and if temporary, the plan to remove them before release.

Also applies to: 97-97, 99-99

pkg/payload/precondition/clusterversion/recommendedupdate.go (1)

14-14: LGTM! Constant centralization improves maintainability.

The refactoring to use internal.ConditionalUpdateConditionTypeRecommended instead of the string literal "Recommended" reduces duplication and the risk of typos across the codebase.

Also applies to: 55-55

pkg/featuregates/featuregates.go (1)

39-40: LGTM! Feature gate implementation follows established patterns.

The AcceptRisks feature gate is properly wired through the entire chain:

  • Interface method declared
  • Struct field added with conservative default (false)
  • Getter method implemented
  • Proper handling in both enable and disable paths

The implementation is consistent with existing feature gates (StatusReleaseArchitecture, CVOConfiguration).

Also applies to: 55-55, 70-72, 81-81, 103-104, 113-114

pkg/cvo/metrics_test.go (1)

50-50: LGTM! Test updated to use centralized constant.

The change to use internal.ConditionalUpdateConditionTypeRecommended is consistent with the constant centralization across the codebase.

pkg/cvo/metrics.go (1)

444-444: LGTM! Metrics collection updated to use centralized constant.

The change to use internal.ConditionalUpdateConditionTypeRecommended maintains the same filtering logic while aligning with the codebase-wide constant centralization.

pkg/cvo/status_test.go (1)

206-206: LGTM! Test infrastructure updated for new feature gate.

The changes properly update the test fakes and function calls:

  • fakeRiFlags now implements the complete CvoGateChecker interface with the new AcceptRisks() method
  • The updateClusterVersionStatus call includes the new shouldReconcileAcceptRisks callback with a conservative default (false) appropriate for this test's focus on error filtering

Also applies to: 221-223, 749-751

pkg/cvo/status.go (6)

20-20: LGTM! Import addition is appropriate.

The sets import is correctly added to support the new risk acceptance functionality.


44-44: LGTM! Constant usage centralized.

The switch to internal.ConditionalUpdateConditionTypeRecommended aligns with the PR's objective of using centralized internal constants.


195-203: LGTM! Function signature properly extended.

The addition of shouldReconcileAcceptRisks func() bool parameter is appropriate for feature-gating the risk acceptance logic, and the call site at line 184 correctly passes optr.shouldReconcileAcceptRisks.


232-236: LGTM! Risk processing logic is properly gated.

The risk-aware processing integration correctly:

  • Gates execution with shouldReconcileAcceptRisks()
  • Computes riskNamesForDesiredImage for the desired image
  • Updates ConditionalUpdates and ConditionalUpdateRisks fields

241-247: LGTM! Risk message construction logic is correct.

The conditional message appending properly handles both scenarios:

  1. Empty risksMsg: constructs a complete sentence
  2. Non-empty risksMsg: appends with appropriate separator

The logic is correctly gated by both shouldReconcileAcceptRisks() and the presence of riskNamesForDesiredImage.


456-469: LGTM! Risk deduplication logic is correct.

The conditionalUpdateRisks function correctly deduplicates risks across all conditional updates using a set-based approach, ensuring each unique risk appears only once in the returned slice.

pkg/cvo/availableupdates_test.go (7)

22-22: LGTM! Import additions support new test functionality.

The sets import is appropriately added to support the new AcceptRisks field handling in tests.


29-29: LGTM! Feature gates import is appropriate.

The featuregates import enables test setup to configure enabledFeatureGates, which is essential for testing the new gated behavior.


211-212: LGTM! Comparison option correctly ignores function field.

Adding ShouldReconcileAcceptRisks to the ignore list is appropriate since function pointers cannot be meaningfully compared in deep equality checks.


237-237: LGTM! Feature gates properly initialized in tests.

The test correctly initializes enabledFeatureGates with DefaultCvoGates before invoking syncAvailableUpdates, ensuring the code under test has access to gate-checking functionality.


515-517: LGTM! Test call signature correctly updated.

The evaluateConditionalUpdate call is properly updated with the new parameters:

  • sets.New[string]() for empty accepted risks
  • func() bool { return false } to disable risk acceptance
  • map[string][]metav1.Condition{} for empty risk conditions

These defaults are appropriate for testing the core evaluation logic in isolation.


326-326: LGTM! Consistent feature gate initialization.

The test setup consistently initializes feature gates before each sync operation.


754-754: LGTM! Feature gate initialization is consistent.

All test cases consistently initialize enabledFeatureGates before calling sync methods, ensuring uniform test behavior.

pkg/cvo/availableupdates.go (11)

19-19: LGTM! Import addition is appropriate.

The sets import correctly supports the new AcceptRisks field of type sets.Set[string].


26-26: LGTM! Internal constants import is appropriate.

The internal package import provides access to centralized constants like ConditionalUpdateConditionTypeRecommended, aligning with the PR's objective of using internal constants.


55-60: LGTM! Nil-safety properly implemented.

The acceptRisks initialization correctly guards against a nil DesiredUpdate by checking config.Spec.DesiredUpdate != nil before accessing its AcceptRisks field. This addresses the previously flagged nil pointer concern.


140-141: LGTM! AvailableUpdates fields properly initialized.

The ShouldReconcileAcceptRisks and AcceptRisks fields are correctly assigned from operator state, enabling risk acceptance logic during update evaluation.


183-184: LGTM! Struct fields appropriately added.

The new fields ShouldReconcileAcceptRisks (gate function) and AcceptRisks (risk name set) are correctly typed and positioned to support the risk acceptance feature.


208-208: LGTM! RiskConditions field appropriately added.

The RiskConditions map field enables per-risk condition tracking, mapping risk names to their associated conditions.


307-312: LGTM with observation: RiskConditions not copied.

The new fields are correctly populated in the copy:

  • Line 311: ShouldReconcileAcceptRisks uses the current operator method (ensuring latest gate state)
  • Line 312: AcceptRisks is copied from stored state

Observation: RiskConditions is not copied here and remains nil in the returned struct. This appears intentional since RiskConditions is populated during evaluateConditionalUpdates. Verify this is the expected behavior.


446-446: LGTM! Function call correctly updated.

The evaluateConditionalUpdate call properly passes the new parameters (u.AcceptRisks, u.ShouldReconcileAcceptRisks, u.RiskConditions) to support risk acceptance evaluation.


505-505: LGTM! Constant appropriately added.

The riskConditionReasonEvaluationFailed constant provides a consistent reason value for risk condition evaluation failures.


523-530: LGTM! Function signature appropriately extended.

The evaluateConditionalUpdate signature is correctly extended with:

  • acceptRisks sets.Set[string]: accepted risk names
  • shouldReconcileAcceptRisks func() bool: feature gate check
  • riskConditions map[string][]metav1.Condition: per-risk condition tracking

All call sites have been updated accordingly.


532-532: LGTM! Constant usage centralized.

The switch to internal.ConditionalUpdateConditionTypeRecommended aligns with the PR's objective of using internal constants consistently.

@hongkailiu hongkailiu force-pushed the accept-cvo branch 2 times, most recently from a46418c to 5eee754 Compare January 2, 2026 18:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
pkg/cvo/availableupdates.go (1)

572-574: Risk conditions may not persist across evaluations and could contain stale data.

Two issues with the risk condition tracking:

  1. Map nil panic (duplicate concern from past review): If riskConditions is nil when line 573 executes, it will panic. This can occur when needFreshFetch is false and the cached RiskConditions is nil (e.g., from an older state before line 142 was added, or if the map reference from line 314 is nil).

  2. Stale conditions on re-evaluation: The check if _, ok := riskConditions[risk.Name]; !ok means existing conditions are never updated. When evaluateConditionalUpdates is called multiple times (e.g., when needFreshFetch is false but conditions need re-evaluation per lines 86-104), the risk conditions remain unchanged from the previous evaluation, potentially leaving stale status/reason/message.

🔎 Recommended fixes

Fix 1: Initialize map before use

In evaluateConditionalUpdates before the loop (after line 446):

 	})
 	for i, conditionalUpdate := range u.ConditionalUpdates {
+		if u.RiskConditions == nil {
+			u.RiskConditions = map[string][]metav1.Condition{}
+		}
 		condition := evaluateConditionalUpdate(ctx, conditionalUpdate.Risks, u.ConditionRegistry, u.AcceptRisks, u.ShouldReconcileAcceptRisks, u.RiskConditions)

Fix 2: Update conditions on re-evaluation

Replace the conditional assignment with unconditional:

-		if _, ok := riskConditions[risk.Name]; !ok {
-			riskConditions[risk.Name] = []metav1.Condition{riskCondition}
-		}
+		riskConditions[risk.Name] = []metav1.Condition{riskCondition}

This ensures the condition is updated on every evaluation, reflecting current state rather than preserving stale data.

🧹 Nitpick comments (1)
pkg/cvo/status.go (1)

425-476: Consider skipping risk-condition reconciliation when getAvailableUpdates() is nil

When shouldReconcileAcceptRisks() is true but getAvailableUpdates() returns nil, conditionalUpdateWithRiskNamesAndRiskConditions treats riskConditions as empty and will remove all existing risk.Conditions from cvStatus.ConditionalUpdates before re‑adding none. If CVO restarts with preexisting conditional updates in status but has not yet refreshed in‑memory availableUpdates, this could transiently clear risk condition details in status. You may want to early‑return the original conditionalUpdates unchanged when updates == nil to preserve prior conditions until fresh data is available.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between cd8eab2 and a46418c.

📒 Files selected for processing (11)
  • pkg/cvo/availableupdates.go
  • pkg/cvo/availableupdates_test.go
  • pkg/cvo/cvo.go
  • pkg/cvo/cvo_test.go
  • pkg/cvo/metrics.go
  • pkg/cvo/metrics_test.go
  • pkg/cvo/status.go
  • pkg/cvo/status_test.go
  • pkg/featuregates/featuregates.go
  • pkg/internal/constants.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
  • pkg/internal/constants.go
  • pkg/cvo/metrics.go
  • pkg/cvo/metrics_test.go
  • pkg/cvo/status_test.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.

Files:

  • pkg/cvo/cvo.go
  • pkg/cvo/status.go
  • pkg/featuregates/featuregates.go
  • pkg/cvo/availableupdates.go
  • pkg/cvo/cvo_test.go
  • pkg/cvo/availableupdates_test.go
🧬 Code graph analysis (1)
pkg/cvo/availableupdates_test.go (3)
pkg/clusterconditions/clusterconditions.go (1)
  • Condition (19-29)
pkg/featuregates/featuregates.go (1)
  • DefaultCvoGates (75-83)
pkg/clusterconditions/mock/mock.go (2)
  • Mock (35-44)
  • MatchResult (14-20)
🔇 Additional comments (11)
pkg/cvo/cvo.go (1)

1095-1102: AcceptRisks reconciliation gate mirrors existing configuration gating correctly

The new shouldReconcileAcceptRisks helper cleanly reuses the feature‑gate plus !hypershift pattern from shouldReconcileCVOConfiguration, giving a clear single decision point for risk‑related reconciliation. Looks good.

pkg/featuregates/featuregates.go (1)

39-41: AcceptRisks feature gate wiring is consistent with existing gates

The AcceptRisks flag is correctly plumbed through the CvoGateChecker interface, CvoGates struct, defaults, and CvoGatesFromFeatureGate enable/disable logic, mirroring the existing gate patterns. Only caveat is that adding a method to CvoGateChecker is a compile‑time breaking change for any external implementations, which is fine if this interface is internal to CVO consumers.

Also applies to: 55-56, 70-72, 81-82, 99-105, 109-115

pkg/cvo/cvo_test.go (1)

19-20: Test harness updates align cleanly with new AcceptRisks behavior

Using cmpopts.IgnoreFields(availableUpdates{}, "ShouldReconcileAcceptRisks") and initializing optr.enabledFeatureGates with featuregates.DefaultCvoGates("version") keeps existing tests focused on functional behavior while accommodating the new gate. The extended checkStatus generic to include *configv1.ClusterVersion fits the existing pattern and maintains clarity of assertions.

Also applies to: 2279-2280, 2792-2794, 4057-4109

pkg/cvo/status.go (1)

43-45: Risk-acceptance status integration and nil-guarding look correct

Using internal.ConditionalUpdateConditionTypeRecommended in findRecommendedCondition removes the hard‑coded string, and threading shouldReconcileAcceptRisks into updateClusterVersionStatus gives a single gate for all accept‑risks behavior. The new helpers cleanly recompute ConditionalUpdates, populate ConditionalUpdateRisks, and, via the local updates := getAvailableUpdates() guard, avoid nil dereferences when availableUpdates is not yet initialized. Overall, the risk‑related status updates and messaging are wired coherently around the feature gate.

Also applies to: 184-186, 194-203, 232-247, 425-476

pkg/cvo/availableupdates_test.go (1)

22-23: Tests thoroughly cover acceptRisks and risk-condition propagation

The new table-driven cases for evaluateConditionalUpdate and the expectations on RiskConditions in the sync tests exercise zero/one/many risks, matches vs. non-matches, evaluation failures, and the “accepted risk” path via acceptRisks/shouldReconcileAcceptRisks. Initializing enabledFeatureGates with featuregates.DefaultCvoGates("version") and ignoring ShouldReconcileAcceptRisks in struct comparisons keeps tests stable while still validating the new behavior.

Also applies to: 211-217, 236-239, 352-372, 418-440, 489-547, 579-583, 795-805

pkg/cvo/availableupdates.go (6)

55-60: LGTM! Nil check properly guards against potential panic.

The nil check on config.Spec.DesiredUpdate correctly addresses the previous review concern about potential nil pointer dereference.


180-210: LGTM! Struct fields appropriately defined.

The new fields integrate well with the existing structure. The function pointer for ShouldReconcileAcceptRisks allows for dynamic evaluation, and the map-based RiskConditions structure is suitable for per-risk condition tracking.


499-509: LGTM! Constants are well-defined.

The new constants appropriately capture the risk acceptance semantics and evaluation failure states.


517-523: LGTM! Reason merging logic handles acceptance states.

The updated logic correctly handles the new recommendedReasonExposedOnlyToAcceptedRisks state, though the conditions are complex. The logic prioritizes maintaining "accepted risks only" status when appropriate.


558-570: LGTM! Risk acceptance logic correctly implemented.

The conditional logic properly handles accepted risks:

  • Risks are only considered "accepted" when both the feature gate (shouldReconcileAcceptRisks()) is enabled AND the risk appears in acceptRisks
  • Accepted risks don't block recommendations (line 559)
  • Non-accepted but exposed risks do block recommendations (line 563)

This correctly implements the risk acceptance feature semantics.


313-314: This review comment is incorrect and should be dismissed.

Lines 313-314 safely shallow-copy AcceptRisks and RiskConditions. The concern about shared map mutations is unfounded—these fields are mutated only during the construction phase in evaluateConditionalUpdates() (called at line 158), not after getAvailableUpdates() returns a read-only snapshot.

The nil panic scenario cannot occur: RiskConditions is always initialized to an empty map at line 142 before any use. The intentional shallow-copy pattern (evidenced by ConditionRegistry at line 318) reflects the design choice to return snapshots for read access to cached state.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between a46418c and 5eee754.

📒 Files selected for processing (11)
  • pkg/cvo/availableupdates.go
  • pkg/cvo/availableupdates_test.go
  • pkg/cvo/cvo.go
  • pkg/cvo/cvo_test.go
  • pkg/cvo/metrics.go
  • pkg/cvo/metrics_test.go
  • pkg/cvo/status.go
  • pkg/cvo/status_test.go
  • pkg/featuregates/featuregates.go
  • pkg/internal/constants.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
  • pkg/cvo/metrics_test.go
  • pkg/cvo/cvo.go
  • pkg/cvo/status_test.go
  • pkg/cvo/metrics.go
  • pkg/internal/constants.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.

Files:

  • pkg/featuregates/featuregates.go
  • pkg/cvo/status.go
  • pkg/cvo/availableupdates_test.go
  • pkg/cvo/cvo_test.go
  • pkg/cvo/availableupdates.go
🧬 Code graph analysis (4)
pkg/cvo/status.go (1)
pkg/internal/constants.go (1)
  • ConditionalUpdateConditionTypeRecommended (76-76)
pkg/cvo/availableupdates_test.go (2)
pkg/featuregates/featuregates.go (1)
  • DefaultCvoGates (75-83)
pkg/cvo/cvo.go (1)
  • New (204-296)
pkg/cvo/cvo_test.go (1)
pkg/clusterconditions/clusterconditions.go (1)
  • Condition (19-29)
pkg/cvo/availableupdates.go (2)
pkg/clusterconditions/clusterconditions.go (2)
  • Condition (19-29)
  • ConditionRegistry (31-42)
pkg/internal/constants.go (2)
  • ConditionalUpdateConditionTypeRecommended (76-76)
  • ConditionalUpdateRiskConditionTypeApplies (80-80)
🔇 Additional comments (5)
pkg/featuregates/featuregates.go (1)

39-40: AcceptRisks feature-gate wiring looks correct and conservative

Interface, struct field, default initialization, and FeatureGate mapping for FeatureGateClusterUpdateAcceptRisks are all consistent and symmetric; defaulting to false in unknown-version mode is appropriately safe.

Also applies to: 55-56, 70-72, 81-82, 103-105, 113-115

pkg/cvo/cvo_test.go (1)

19-20: Updated availableUpdates expectations and comparison are sound

Using cmp.Diff with cmpopts.IgnoreFields(availableUpdates{}, "ShouldReconcileAcceptRisks") and explicitly asserting RiskConditions in the test table aligns the tests with the new risk-tracking fields while avoiding brittle comparisons on function fields and timestamps.

Also applies to: 2357-2370, 2401-2412, 2445-2455, 2495-2505, 2559-2574, 2627-2641, 2729-2741, 2792-2794

pkg/cvo/availableupdates_test.go (1)

14-16: Tests comprehensively cover new risk-acceptance and RiskConditions behavior

The additional cmp options, RiskConditions expectations, and extended TestEvaluateConditionalUpdate / desired-update tests exercise the new AcceptRisks and per-risk condition flows well, while guarding against nil-map issues and non-deterministic fields.

Also applies to: 21-23, 211-217, 219-247, 352-372, 374-417, 418-440, 462-547, 549-571, 579-583, 591-605, 610-782

pkg/cvo/status.go (1)

190-201: Risk-aware status and history updates are gated cleanly

Plumbing getAvailableUpdates and shouldReconcileAcceptRisks into updateClusterVersionStatus, and only enriching ConditionalUpdates, ConditionalUpdateRisks, and AcceptedRisks/history when the gate is on, keeps existing behavior unchanged while the feature is disabled and localizes the new logic well.

Also applies to: 223-232, 234-247

pkg/cvo/availableupdates.go (1)

19-27: AcceptRisks and per-risk condition tracking are wired coherently end-to-end

Building acceptRisks from spec.desiredUpdate.acceptRisks, threading it plus ShouldReconcileAcceptRisks and RiskConditions through availableUpdates into evaluateConditionalUpdate, and using internal constants for condition types yields a consistent, feature-gated behavior without introducing nil-map or concurrency hazards at current call sites.

Also applies to: 52-60, 136-143, 180-210, 220-259, 298-337, 437-460, 498-509, 526-581

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Jan 4, 2026

@hongkailiu: This pull request references OTA-1546 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

From this pull, CVO starts to reconcile cv.spec.desiredUpdate.acceptRisks
on a cluser if FeatureGateClusterUpdateAcceptRisks is enabled no a
non-hypershift cluster.

  • CVO does not block a conditional update if all risks that apply
    to the cluster are accpeted.

  • CVO fills up the new fields in status:

  • status.conditionalUpdateRisks,

  • status.conditionalUpdate.riskNames, and

  • status.conditionalUpdate.risks.conditions

  • CVO adds risk names into status.history.acceptedRisks if a conditional
    update is accepted.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 5eee754 and 220ca42.

📒 Files selected for processing (11)
  • pkg/cvo/availableupdates.go
  • pkg/cvo/availableupdates_test.go
  • pkg/cvo/cvo.go
  • pkg/cvo/cvo_test.go
  • pkg/cvo/metrics.go
  • pkg/cvo/metrics_test.go
  • pkg/cvo/status.go
  • pkg/cvo/status_test.go
  • pkg/featuregates/featuregates.go
  • pkg/internal/constants.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/cvo/metrics.go
  • pkg/cvo/metrics_test.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.

Files:

  • pkg/cvo/cvo.go
  • pkg/featuregates/featuregates.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
  • pkg/cvo/status_test.go
  • pkg/internal/constants.go
  • pkg/cvo/status.go
  • pkg/cvo/cvo_test.go
  • pkg/cvo/availableupdates.go
  • pkg/cvo/availableupdates_test.go
🧬 Code graph analysis (3)
pkg/payload/precondition/clusterversion/recommendedupdate.go (1)
pkg/internal/constants.go (1)
  • ConditionalUpdateConditionTypeRecommended (76-76)
pkg/cvo/availableupdates.go (2)
pkg/clusterconditions/clusterconditions.go (2)
  • Condition (19-29)
  • ConditionRegistry (31-42)
pkg/internal/constants.go (2)
  • ConditionalUpdateConditionTypeRecommended (76-76)
  • ConditionalUpdateRiskConditionTypeApplies (80-80)
pkg/cvo/availableupdates_test.go (3)
pkg/featuregates/featuregates.go (1)
  • DefaultCvoGates (75-83)
pkg/clusterconditions/mock/mock.go (2)
  • Mock (35-44)
  • MatchResult (14-20)
pkg/cvo/cvo.go (1)
  • New (204-296)
🔇 Additional comments (9)
pkg/payload/precondition/clusterversion/recommendedupdate.go (1)

14-14: LGTM! Improved maintainability.

Replacing the string literal "Recommended" with the centralized constant internal.ConditionalUpdateConditionTypeRecommended improves maintainability and consistency across the codebase. This follows best practices for avoiding magic strings.

Also applies to: 55-55

pkg/internal/constants.go (1)

74-80: LGTM! Well-documented constants.

The new constants ConditionalUpdateConditionTypeRecommended and ConditionalUpdateRiskConditionTypeApplies centralize string literals for conditional update condition types. The clear documentation and public visibility support consistent usage across the codebase, improving maintainability.

pkg/featuregates/featuregates.go (1)

39-40: LGTM! Consistent feature gate implementation.

The AcceptRisks feature gate implementation follows the established pattern used by other gates in this file (StatusReleaseArchitecture, CVOConfiguration):

  • Interface method, private struct field, and accessor method
  • Default value of false is safe and conservative
  • Enable and disable cases correctly toggle the flag

This consistency makes the code predictable and maintainable.

Also applies to: 55-55, 70-72, 81-81, 103-104, 113-114

pkg/cvo/cvo.go (1)

1095-1102: LGTM! Consistent gating pattern.

The shouldReconcileAcceptRisks() method follows the same pattern as shouldReconcileCVOConfiguration():

  • Checks the feature gate
  • Excludes HyperShift environments
  • Has clear documentation

This consistency in gating logic makes the codebase predictable and maintainable.

pkg/cvo/status_test.go (3)

206-206: LGTM! Test scaffolding properly extended.

The fakeRiFlags test struct now implements the complete CvoGateChecker interface by adding the acceptRisks field and AcceptRisks() method. This enables testing of the new feature gate behavior.

Also applies to: 221-223


749-751: LGTM! Appropriate gate state for tests.

The updated call to updateClusterVersionStatus passes a function returning false for shouldReconcileAcceptRisks, which correctly tests the behavior when the feature gate is disabled.


946-971: LGTM! Comprehensive test coverage.

The new test data (cu1, cu2) and test functions (Test_conditionalUpdateWithRiskNamesAndRiskConditions, Test_conditionalUpdateRisks) provide good coverage for the risk-related logic. The use of DeepCopy() (lines 984, 1002, 1019, 1042) correctly prevents test data mutation issues.

Also applies to: 973-1149

pkg/cvo/availableupdates_test.go (1)

211-217: Risk-aware tests and cmp options look consistent with the new semantics

The additions here (ignoring ShouldReconcileAcceptRisks via availableUpdatesCmpOpts, populating RiskConditions expectations, and extending TestEvaluateConditionalUpdate / TestSyncAvailableUpdatesDesiredUpdate to cover accept‑risks and per‑risk conditions) line up with the implementation in availableupdates.go. The defaulting of shouldReconcileAcceptRisks and riskConditions in the tests also matches the production contract.

No issues from my side on these changes.

Also applies to: 236-247, 352-578, 598-835

pkg/cvo/status.go (1)

39-41: Risk wiring and status helpers are coherent with the new accept-risks flow

  • Switching findRecommendedCondition to use internal.ConditionalUpdateConditionTypeRecommended ensures consistent type usage across the codebase.
  • updateClusterVersionStatus now:
    • Safely derives riskNamesForDesiredImage via conditionalUpdateWithRiskNamesAndRiskConditions, and only when shouldReconcileAcceptRisks() is true.
    • Populates ConditionalUpdateRisks via conditionalUpdateRisks, giving consumers a flatter view of risks.
    • Extends risksMsg with a clear explanation when desired image equals the loaded payload and there are accepted risks associated with that image.
  • conditionalUpdateWithRiskNamesAndRiskConditions is defensive against getAvailableUpdates() being nil and realigns risk conditions from RiskConditions back into cvStatus.ConditionalUpdates without risking panics.

This all looks reasonable and correctly scoped to the accept‑risks feature.

Also applies to: 180-199, 228-247, 425-489

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
pkg/cvo/availableupdates.go (1)

576-578: riskConditions entries are not refreshed on re-evaluation.

When needFreshFetch=false but needsConditionalUpdateEval=true, the RiskConditions map is reused from the previous fetch (via getAvailableUpdates() at line 63). The if !ok guard prevents updating existing entries, so risk conditions can become stale (e.g., an "Applies=Unknown" risk later becoming "Applies=True" won't be reflected).

Consider always overwriting the entry to ensure RiskConditions reflects the latest evaluation:

🔎 Proposed fix
-		if _, ok := riskConditions[risk.Name]; !ok {
-			riskConditions[risk.Name] = []metav1.Condition{riskCondition}
-		}
+		// Always refresh the risk condition to reflect the latest evaluation
+		riskConditions[risk.Name] = []metav1.Condition{riskCondition}

This aligns with the past review comment that was marked as addressed but the fix appears incomplete.

🧹 Nitpick comments (2)
pkg/cvo/availableupdates.go (1)

520-526: Consider adding parentheses for clarity in reason precedence logic.

The operator precedence in these conditions makes the logic hard to follow:

  • Line 520: now == ... || now == ... && now != want&& binds tighter than ||
  • Line 522: now == ... && want == ... || now == want

While the logic may be correct, explicit parentheses would improve readability and prevent accidental bugs during future modifications.

🔎 Suggested clarification
 func newRecommendedReason(now, want string) string {
 	switch {
-	case now == recommendedReasonRisksNotExposed || now == recommendedReasonExposedOnlyToAcceptedRisks && now != want:
+	case now == recommendedReasonRisksNotExposed || (now == recommendedReasonExposedOnlyToAcceptedRisks && now != want):
 		return want
-	case now == recommendedReasonExposedOnlyToAcceptedRisks && want == recommendedReasonRisksNotExposed || now == want:
+	case (now == recommendedReasonExposedOnlyToAcceptedRisks && want == recommendedReasonRisksNotExposed) || now == want:
 		return now
 	default:
 		return recommendedReasonMultiple
 	}
 }
pkg/cvo/status.go (1)

440-448: Consider adding a warning log for the fallback condition.

The defensive fallback for missing risk conditions (reason InternalErrorNoConditionCollected) indicates an unexpected state. A warning log would help diagnose issues in production without requiring additional debugging.

🔎 Suggested enhancement
 		conditions, ok := riskConditions[risk.Name]
 		if !ok {
 			// This should never happen
+			klog.Warningf("No conditions collected for risk %q, using fallback", risk.Name)
 			conditions = []metav1.Condition{
 				{
 					Type:   internal.ConditionalUpdateRiskConditionTypeApplies,
 					Status: metav1.ConditionUnknown,
 					Reason: "InternalErrorNoConditionCollected",
 				},
 			}
 		}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 220ca42 and b28ef90.

📒 Files selected for processing (11)
  • pkg/cvo/availableupdates.go
  • pkg/cvo/availableupdates_test.go
  • pkg/cvo/cvo.go
  • pkg/cvo/cvo_test.go
  • pkg/cvo/metrics.go
  • pkg/cvo/metrics_test.go
  • pkg/cvo/status.go
  • pkg/cvo/status_test.go
  • pkg/featuregates/featuregates.go
  • pkg/internal/constants.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • pkg/cvo/metrics_test.go
  • pkg/cvo/metrics.go
  • pkg/cvo/cvo_test.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
  • pkg/cvo/status_test.go
  • pkg/internal/constants.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.

Files:

  • pkg/cvo/cvo.go
  • pkg/cvo/availableupdates.go
  • pkg/cvo/status.go
  • pkg/featuregates/featuregates.go
  • pkg/cvo/availableupdates_test.go
🧬 Code graph analysis (3)
pkg/cvo/availableupdates.go (2)
pkg/clusterconditions/clusterconditions.go (2)
  • Condition (19-29)
  • ConditionRegistry (31-42)
pkg/internal/constants.go (2)
  • ConditionalUpdateConditionTypeRecommended (76-76)
  • ConditionalUpdateRiskConditionTypeApplies (80-80)
pkg/cvo/status.go (2)
pkg/internal/constants.go (1)
  • ConditionalUpdateConditionTypeRecommended (76-76)
pkg/featuregates/featuregates.go (1)
  • CvoGateChecker (19-41)
pkg/cvo/availableupdates_test.go (3)
pkg/clusterconditions/clusterconditions.go (1)
  • Condition (19-29)
pkg/featuregates/featuregates.go (1)
  • DefaultCvoGates (75-83)
pkg/clusterconditions/mock/mock.go (2)
  • Mock (35-44)
  • MatchResult (14-20)
🔇 Additional comments (8)
pkg/featuregates/featuregates.go (1)

38-40: LGTM! Clean feature gate wiring.

The AcceptRisks feature gate follows the established pattern used by other CVO gates (StatusReleaseArchitecture, CVOConfiguration). The default-off behavior is appropriately conservative, and both enable/disable paths are handled correctly in CvoGatesFromFeatureGate.

Also applies to: 55-55, 70-72, 81-81, 103-104, 113-114

pkg/cvo/availableupdates.go (2)

55-60: LGTM! Nil guard properly implemented.

The nil check for config.Spec.DesiredUpdate before accessing AcceptRisks is correctly in place, preventing potential panics.


140-142: LGTM! New fields properly wired.

The new ShouldReconcileAcceptRisks, AcceptRisks, and RiskConditions fields are correctly initialized on fresh fetches and properly copied in getAvailableUpdates(). The map initialization at line 142 ensures no nil-map panics during conditional update evaluation.

Also applies to: 180-212

pkg/cvo/cvo.go (1)

1094-1102: LGTM! Consistent gating pattern.

The shouldReconcileAcceptRisks method correctly mirrors the established pattern from shouldReconcileCVOConfiguration, with appropriate HyperShift exclusion and feature gate checking.

pkg/cvo/availableupdates_test.go (2)

352-577: LGTM! Comprehensive test coverage for risk acceptance.

The test cases thoroughly cover:

  • No risks scenario
  • Single risk that doesn't match
  • Single risk that matches (rejected)
  • Single risk that matches and is accepted (new scenario)
  • Multiple risks with mixed matches
  • Evaluation failures

The default initialization at lines 560-567 ensures shouldReconcileAcceptRisks returns false when not explicitly set, preventing accidental test pollution.


472-498: No action required. The ValidQueue size mismatch is not a problem because Valid() is never called during the execution of this test case. The evaluation logic only calls Match(), not PruneInvalid(), so the ValidQueue setup is unused.

Likely an incorrect or invalid review comment.

pkg/cvo/status.go (2)

425-473: LGTM! Past issues properly addressed.

The function correctly handles:

  1. Nil check for getAvailableUpdates() at lines 429-432 (prevents nil pointer dereference)
  2. riskNamesForDesiredImage is captured at line 469 after conditionalUpdate.RiskNames is computed at line 464 (correct ordering)
  3. Fallback condition at lines 442-448 handles missing risk conditions gracefully

191-199: LGTM! Clean risk-aware status processing.

The extended updateClusterVersionStatus signature properly threads shouldReconcileAcceptRisks through the call chain. The conditional processing at lines 229-232 only activates when the feature gate is enabled, and the risk message construction at lines 241-247 provides clear, informative status messages.

Also applies to: 228-248

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
pkg/cvo/availableupdates.go (1)

576-578: riskConditions entries are not refreshed on re-evaluation.

The if !ok guard only inserts a condition when the key is missing. On re-evaluations without a fresh fetch (when needFreshFetch=false but needsConditionalUpdateEval=true), existing entries are preserved and never updated, causing stale conditions to persist even when cluster state changes.

Always overwrite to reflect the latest evaluation:

-		if _, ok := riskConditions[risk.Name]; !ok {
-			riskConditions[risk.Name] = []metav1.Condition{riskCondition}
-		}
+		riskConditions[risk.Name] = []metav1.Condition{riskCondition}
🧹 Nitpick comments (1)
pkg/cvo/availableupdates_test.go (1)

352-575: EvaluateConditionalUpdate tests thoroughly cover risk evaluation and acceptance

The expanded table tests (including accepted risks, multiple risks, and evaluation failures) plus explicit riskConditions assertions give good coverage of the new API shape and behavior. You might consider one additional case where acceptRisks is non-empty but shouldReconcileAcceptRisks returns false, to lock in that acceptance is ignored when the feature-gate is off, but this can be deferred.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between b28ef90 and 32fa03f.

📒 Files selected for processing (11)
  • pkg/cvo/availableupdates.go
  • pkg/cvo/availableupdates_test.go
  • pkg/cvo/cvo.go
  • pkg/cvo/cvo_test.go
  • pkg/cvo/metrics.go
  • pkg/cvo/metrics_test.go
  • pkg/cvo/status.go
  • pkg/cvo/status_test.go
  • pkg/featuregates/featuregates.go
  • pkg/internal/constants.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/internal/constants.go
  • pkg/cvo/metrics_test.go
  • pkg/payload/precondition/clusterversion/recommendedupdate.go
  • pkg/cvo/metrics.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.

Files:

  • pkg/cvo/status_test.go
  • pkg/cvo/cvo.go
  • pkg/featuregates/featuregates.go
  • pkg/cvo/availableupdates_test.go
  • pkg/cvo/status.go
  • pkg/cvo/cvo_test.go
  • pkg/cvo/availableupdates.go
🧬 Code graph analysis (2)
pkg/cvo/availableupdates_test.go (2)
pkg/featuregates/featuregates.go (1)
  • DefaultCvoGates (75-83)
pkg/clusterconditions/mock/mock.go (2)
  • Mock (35-44)
  • MatchResult (14-20)
pkg/cvo/status.go (2)
pkg/internal/constants.go (2)
  • ConditionalUpdateConditionTypeRecommended (76-76)
  • ConditionalUpdateRiskConditionTypeApplies (80-80)
pkg/featuregates/featuregates.go (1)
  • CvoGateChecker (19-41)
🔇 Additional comments (15)
pkg/cvo/cvo.go (1)

1095-1102: AcceptRisks reconciliation gate matches configuration gate pattern

Gating on enabledFeatureGates.AcceptRisks() and !optr.hypershift is consistent with shouldReconcileCVOConfiguration and the PR’s stated behavior; no issues here.

pkg/cvo/status_test.go (1)

202-224: Risk-name/condition tests and gate-plumbing look correct

Extending fakeRiFlags with AcceptRisks(), threading the new shouldReconcileAcceptRisks callback into updateClusterVersionStatus, and the new conditionalUpdate* tests collectively exercise the new risk-name and risk-condition behavior well, without mutating shared fixtures or introducing flakes.

Also applies to: 749-751, 946-1149

pkg/cvo/cvo_test.go (1)

19-20: cmp.Diff + field-ignore keeps tests stable around new gate field

Switching to cmp.Diff and ignoring ShouldReconcileAcceptRisks isolates tests from this internal gate while still asserting all other availableUpdates state, which is appropriate here.

Also applies to: 2792-2793

pkg/featuregates/featuregates.go (1)

39-41: AcceptRisks gate wiring is consistent and conservative

Adding AcceptRisks to CvoGateChecker/CvoGates, defaulting it to false, and toggling it with FeatureGateClusterUpdateAcceptRisks mirrors the existing gate patterns and provides a safe default when version gating is unknown.

Also applies to: 55-56, 70-72, 81-82, 103-105, 113-115

pkg/cvo/availableupdates_test.go (1)

22-23: Gating and RiskConditions expectations are wired correctly into available-updates tests

Ignoring ShouldReconcileAcceptRisks in comparisons, asserting RiskConditions, and initializing enabledFeatureGates via featuregates.DefaultCvoGates("version") keeps the tests aligned with the new risk-acceptance behavior without over-relaxing assertions.

Also applies to: 29-30, 211-217, 236-239, 327-328, 236-237, 811-823

pkg/cvo/status.go (5)

39-41: LGTM!

Using the centralized internal.ConditionalUpdateConditionTypeRecommended constant improves consistency across the codebase.


191-199: LGTM!

The expanded signature properly threads the feature gate callback through to enable conditional risk reconciliation.


228-247: LGTM!

The risk message construction correctly handles both scenarios (empty and non-empty initial risksMsg), and the feature gate check ensures this logic only runs when enabled.


425-474: LGTM!

The nil check for getAvailableUpdates() and the corrected ordering (capturing riskNamesForDesiredImage after computing RiskNames) properly address the previously identified issues.


476-489: LGTM!

The deduplication logic correctly aggregates unique risks across all conditional updates.

pkg/cvo/availableupdates.go (5)

55-60: LGTM!

The nil guard for config.Spec.DesiredUpdate properly prevents the potential panic identified previously.


140-142: LGTM!

Proper initialization of new fields including the empty map for RiskConditions when performing a fresh fetch.


311-322: Verify: RiskConditions is a shallow copy (map reference).

Unlike Updates and ConditionalUpdates which are deep-copied (lines 325-337), RiskConditions is assigned directly, meaning mutations to the returned struct's RiskConditions will affect the cached optr.availableUpdates.RiskConditions. This may be intentional for cache coherence, but confirm this is the desired behavior.


450-451: LGTM!

The call site properly threads through the new risk-related parameters.


518-527: Verify operator precedence in the condition.

Line 520: now == recommendedReasonRisksNotExposed || now == recommendedReasonExposedOnlyToAcceptedRisks && now != want

Due to operator precedence (&& binds tighter than ||), this parses as:
now == recommendedReasonRisksNotExposed || (now == recommendedReasonExposedOnlyToAcceptedRisks && now != want)

If the intent was to return want when now is either "safe" reason AND differs from want, consider adding parentheses for clarity or confirm this logic is intentional.

@hongkailiu hongkailiu mentioned this pull request Jan 12, 2026
@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jan 23, 2026
@openshift-merge-robot openshift-merge-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jan 23, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pkg/cvo/availableupdates.go`:
- Around line 55-60: The code only updates acceptRisks (and the gate) inside the
needFreshFetch branch so changes to config.Spec.DesiredUpdate.AcceptRisks are
ignored until the next fetch; move or duplicate the logic that builds
acceptRisks (and sets the gate function) so it runs on every sync path (i.e.
outside the needFreshFetch conditional) using config.Spec.DesiredUpdate to
refresh the sets and gate each sync, and apply the same change to the other
occurrence referenced (the second acceptRisks/gate assignment around lines
140-142) so both code paths always reflect spec changes.
♻️ Duplicate comments (1)
pkg/cvo/availableupdates.go (1)

529-578: Always refresh RiskConditions entries on re-evaluation.

The current guard only writes when a key is missing, so risk conditions can become stale across evaluations. Overwrite on every pass so status reflects the latest exposure.

🛠️ Suggested fix
-		if _, ok := riskConditions[risk.Name]; !ok {
-			riskConditions[risk.Name] = []metav1.Condition{riskCondition}
-		}
+		riskConditions[risk.Name] = []metav1.Condition{riskCondition}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pkg/cvo/availableupdates.go`:
- Around line 55-60: The acceptRisks set is only applied to optrAvailableUpdates
inside the needFreshFetch path, leaving optrAvailableUpdates.AcceptRisks stale
when needFreshFetch is false; update the code to always refresh
optrAvailableUpdates.AcceptRisks and
optrAvailableUpdates.ShouldReconcileAcceptRisks after constructing acceptRisks
(from config.Spec.DesiredUpdate.AcceptRisks) regardless of needFreshFetch
outcome—i.e., build acceptRisks as you already do, then after the needFreshFetch
conditional assign optrAvailableUpdates.AcceptRisks = acceptRisks and
recompute/set optrAvailableUpdates.ShouldReconcileAcceptRisks before returning
or using optrAvailableUpdates so spec changes take effect immediately
(references: acceptRisks, optrAvailableUpdates, needFreshFetch,
getAvailableUpdates, ShouldReconcileAcceptRisks,
config.Spec.DesiredUpdate.AcceptRisks).
♻️ Duplicate comments (1)
pkg/cvo/availableupdates.go (1)

576-578: Remove the "only if missing" guard and always update risk conditions.

The guard at lines 576-578 should be removed, though not for the stated reason in the original review. The original claim about "re-evaluation without fresh fetch" is incorrect—RiskConditions is always reset to an empty map (line 142) before evaluateConditionalUpdates is called. The guard's only real effect would be to skip updates if the same risk name appears in multiple ConditionalUpdates within a single evaluation cycle, which is not a realistic scenario based on current usage patterns.

However, removing the guard and always updating the condition is still the correct approach for defensive coding. It ensures that if risk evaluations change, the latest state is reflected, and it aligns with the expectation in status.go (line 440-441) that all evaluated risks will have entries in RiskConditions.

-		if _, ok := riskConditions[risk.Name]; !ok {
-			riskConditions[risk.Name] = []metav1.Condition{riskCondition}
-		}
+		riskConditions[risk.Name] = []metav1.Condition{riskCondition}
🧹 Nitpick comments (2)
pkg/cvo/status_test.go (1)

945-1138: Consider using the internal Applies condition constant to avoid string drift.
This keeps tests aligned with production constants and avoids typo risk.

♻️ Proposed refactor
+const appliesConditionType = internal.ConditionalUpdateRiskConditionTypeApplies
+
 var (
 	cu1 = configv1.ConditionalUpdate{
 		Release:   configv1.Release{Version: "4.5.6", Image: "pullspec/4.5.6"},
 		RiskNames: []string{"wrongName"},
@@
-							Type:   "Applies",
+							Type:   appliesConditionType,
@@
-						Type:   "Applies",
+						Type:   appliesConditionType,
@@
-						Type:   "Applies",
+						Type:   appliesConditionType,
@@
-						Type:   "Applies",
+						Type:   appliesConditionType,
@@
-						Type:   "Applies",
+						Type:   appliesConditionType,
@@
-						Type:   "Applies",
+						Type:   appliesConditionType,
@@
-						Type:   "Applies",
+						Type:   appliesConditionType,
@@
-						Type:   "Applies",
+						Type:   appliesConditionType,
@@
-						Type:   "Applies",
+						Type:   appliesConditionType,
@@
-					Type:   "Applies",
+					Type:   appliesConditionType,
@@
-					Type:   "Applies",
+					Type:   appliesConditionType,
@@
-					Type:   "Applies",
+					Type:   appliesConditionType,
@@
-					Type:   "Applies",
+					Type:   appliesConditionType,
pkg/cvo/availableupdates.go (1)

518-527: Verify reason priority logic.

The newRecommendedReason function has complex conditions for handling the new ExposedOnlyToAcceptedRisks reason:

  • Line 520: Returns want if current is NotExposed or (ExposedOnlyToAcceptedRisks AND different from want)
  • Line 522: Returns current if current is ExposedOnlyToAcceptedRisks and want is NotExposed, or if they're equal

This implements priority where exposed risks take precedence over accepted risks, which take precedence over not-exposed. The logic appears correct but is subtle.

Consider adding a brief comment explaining the priority
 func newRecommendedReason(now, want string) string {
+	// Priority: Exposed/EvaluationFailed > ExposedOnlyToAcceptedRisks > NotExposedToRisks
 	switch {
 	case now == recommendedReasonRisksNotExposed || now == recommendedReasonExposedOnlyToAcceptedRisks && now != want:

From this pull, CVO starts to reconcile `cv.spec.desiredUpdate.acceptRisks`
on a cluser if FeatureGateClusterUpdateAcceptRisks is enabled no a
non-hypershift cluster.

- CVO does not block a conditional update if all risks that apply
  to the cluster are accpeted.
- CVO fills up the new fields in status:

  - status.conditionalUpdateRisks,
  - status.conditionalUpdate.riskNames, and
  - status.conditionalUpdate.risks.conditions

- CVO adds risk names into status.history.acceptedRisks if a conditional
  update is accepted.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@pkg/cvo/status.go`:
- Around line 463-466: The code only assigns conditionalUpdate.RiskNames when
riskNames.Len() > 0, leaving stale values when risks are cleared; change this to
always set conditionalUpdate.RiskNames from riskNames (i.e., remove the Len()
check and unconditionally assign conditionalUpdate.RiskNames =
sets.List[string](riskNames)) so that RiskNames accurately reflects the current
conditionalUpdate.Risks even when empty.

In `@test/cvo/accept_risks.go`:
- Around line 61-89: Backup/restoration logic and assertions around
cv.Spec.DesiredUpdate.AcceptRisks are unsafe and inverted: preserve the original
bk (already DeepCopied), and when restoring set cv.Spec.DesiredUpdate = bk;
after the update, avoid dereferencing when cv.Spec.DesiredUpdate is nil by
checking for nil first and then assert absence of the previously-added risks
(use BeFalse for slices.Contains for riskA/riskB) or, if bk was non-nil, assert
that the restored AcceptRisks equals the original bk.AcceptRisks (or contains
the same names); reference bk, cv.Spec.DesiredUpdate and AcceptRisks when making
the nil-safe checks and flipping the assertions in the "Checking that new risks
can be removed" section.
♻️ Duplicate comments (3)
pkg/cvo/cvo_test.go (1)

2791-2792: Avoid AcceptRisks nil/empty diffs in availableUpdates comparison.

AcceptRisks can be initialized to an empty set even when fixtures leave it nil, which can create deterministic diffs. Consider ignoring it (or initializing in expectations).

🛠️ Suggested fix
-			if diff := cmp.Diff(tt.wantUpdates, optr.availableUpdates, cmpopts.IgnoreFields(availableUpdates{}, "ShouldReconcileAcceptRisks")); diff != "" {
+			if diff := cmp.Diff(
+				tt.wantUpdates,
+				optr.availableUpdates,
+				cmpopts.IgnoreFields(availableUpdates{}, "ShouldReconcileAcceptRisks", "AcceptRisks"),
+			); diff != "" {
 				t.Fatalf("unexpected: %s", diff)
 			}
pkg/cvo/availableupdates.go (2)

529-578: RiskConditions are not refreshed on re‑evaluation.
The riskConditions map is only written when the key is missing, so re‑evaluation keeps stale conditions and status can drift from current cluster state.

🔧 Proposed fix
-		if _, ok := riskConditions[risk.Name]; !ok {
-			riskConditions[risk.Name] = []metav1.Condition{riskCondition}
-		}
+		// Always refresh to reflect the latest evaluation.
+		riskConditions[risk.Name] = []metav1.Condition{riskCondition}

55-60: Refresh accept‑risk inputs even when reusing cached updates.
AcceptRisks and ShouldReconcileAcceptRisks are only assigned inside the needFreshFetch path, so changes to spec.desiredUpdate.acceptRisks won’t take effect until a fresh fetch. Please refresh these after the fetch decision.

🔧 Proposed fix
 	if needFreshFetch {
 		// ...
-		optrAvailableUpdates.ShouldReconcileAcceptRisks = optr.shouldReconcileAcceptRisks
-		optrAvailableUpdates.AcceptRisks = acceptRisks
 		optrAvailableUpdates.RiskConditions = map[string][]metav1.Condition{}
 		// ...
 	}
+
+	// Always refresh accept-risk inputs even when skipping a fetch.
+	optrAvailableUpdates.ShouldReconcileAcceptRisks = optr.shouldReconcileAcceptRisks
+	optrAvailableUpdates.AcceptRisks = acceptRisks

Also applies to: 140-143

Comment on lines +463 to +466
if riskNames.Len() > 0 {
conditionalUpdate.RiskNames = sets.List[string](riskNames)
}
conditionalUpdate.Risks = risks
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clear stale RiskNames when no risks remain.

Right now RiskNames is only updated when the set is non-empty, so a previous non-empty list can persist after risks are removed, leading to stale status/history messaging. Assign it unconditionally to reflect the current Risks.

🛠️ Suggested fix
-		if riskNames.Len() > 0 {
-			conditionalUpdate.RiskNames = sets.List[string](riskNames)
-		}
+		conditionalUpdate.RiskNames = sets.List[string](riskNames)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if riskNames.Len() > 0 {
conditionalUpdate.RiskNames = sets.List[string](riskNames)
}
conditionalUpdate.Risks = risks
conditionalUpdate.RiskNames = sets.List[string](riskNames)
conditionalUpdate.Risks = risks
🤖 Prompt for AI Agents
In `@pkg/cvo/status.go` around lines 463 - 466, The code only assigns
conditionalUpdate.RiskNames when riskNames.Len() > 0, leaving stale values when
risks are cleared; change this to always set conditionalUpdate.RiskNames from
riskNames (i.e., remove the Len() check and unconditionally assign
conditionalUpdate.RiskNames = sets.List[string](riskNames)) so that RiskNames
accurately reflects the current conditionalUpdate.Risks even when empty.

Comment on lines 61 to 89
bk := cv.Spec.DesiredUpdate
if bk != nil {
bk = bk.DeepCopy()
}

g.By("Checking that new risks can be saved")
if cv.Spec.DesiredUpdate == nil {
cv.Spec.DesiredUpdate = &configv1.Update{}
}
cv.Spec.DesiredUpdate.AcceptRisks = append(cv.Spec.DesiredUpdate.AcceptRisks, []configv1.AcceptRisk{
{Name: "riskA"},
{Name: "riskB"},
}...)

cv, err = configv1Client.ClusterVersions().Update(context.Background(), cv, metav1.UpdateOptions{})
o.Expect(err).NotTo(o.HaveOccurred())
var names []string
for _, r := range cv.Spec.DesiredUpdate.AcceptRisks {
names = append(names, r.Name)
}
o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskA"})).To(o.BeTrue())
o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskB"})).To(o.BeTrue())

g.By("Checking that new risks can be removed")
cv.Spec.DesiredUpdate = bk
cv, err = configv1Client.ClusterVersions().Update(context.Background(), cv, metav1.UpdateOptions{})
o.Expect(err).NotTo(o.HaveOccurred())
o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskA"})).To(o.BeTrue())
o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskB"})).To(o.BeTrue())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix nil deref and inverted “remove risks” assertions.

If the original desiredUpdate is nil, Line 85 restores it to nil and Line 88 dereferences it, which can panic. Also, the “removed” step still asserts that risks are present. Consider restoring safely and asserting absence (or matching the backup).

🛠️ Suggested fix
 		g.By("Checking that new risks can be removed")
-		cv.Spec.DesiredUpdate = bk
+		if bk == nil {
+			cv.Spec.DesiredUpdate = nil
+		} else {
+			cv.Spec.DesiredUpdate = bk
+		}
 		cv, err = configv1Client.ClusterVersions().Update(context.Background(), cv, metav1.UpdateOptions{})
 		o.Expect(err).NotTo(o.HaveOccurred())
-		o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskA"})).To(o.BeTrue())
-		o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskB"})).To(o.BeTrue())
+		if cv.Spec.DesiredUpdate != nil {
+			o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskA"})).To(o.BeFalse())
+			o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskB"})).To(o.BeFalse())
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bk := cv.Spec.DesiredUpdate
if bk != nil {
bk = bk.DeepCopy()
}
g.By("Checking that new risks can be saved")
if cv.Spec.DesiredUpdate == nil {
cv.Spec.DesiredUpdate = &configv1.Update{}
}
cv.Spec.DesiredUpdate.AcceptRisks = append(cv.Spec.DesiredUpdate.AcceptRisks, []configv1.AcceptRisk{
{Name: "riskA"},
{Name: "riskB"},
}...)
cv, err = configv1Client.ClusterVersions().Update(context.Background(), cv, metav1.UpdateOptions{})
o.Expect(err).NotTo(o.HaveOccurred())
var names []string
for _, r := range cv.Spec.DesiredUpdate.AcceptRisks {
names = append(names, r.Name)
}
o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskA"})).To(o.BeTrue())
o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskB"})).To(o.BeTrue())
g.By("Checking that new risks can be removed")
cv.Spec.DesiredUpdate = bk
cv, err = configv1Client.ClusterVersions().Update(context.Background(), cv, metav1.UpdateOptions{})
o.Expect(err).NotTo(o.HaveOccurred())
o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskA"})).To(o.BeTrue())
o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskB"})).To(o.BeTrue())
g.By("Checking that new risks can be removed")
if bk == nil {
cv.Spec.DesiredUpdate = nil
} else {
cv.Spec.DesiredUpdate = bk
}
cv, err = configv1Client.ClusterVersions().Update(context.Background(), cv, metav1.UpdateOptions{})
o.Expect(err).NotTo(o.HaveOccurred())
if cv.Spec.DesiredUpdate != nil {
o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskA"})).To(o.BeFalse())
o.Expect(slices.Contains(cv.Spec.DesiredUpdate.AcceptRisks, configv1.AcceptRisk{Name: "riskB"})).To(o.BeFalse())
}
🤖 Prompt for AI Agents
In `@test/cvo/accept_risks.go` around lines 61 - 89, Backup/restoration logic and
assertions around cv.Spec.DesiredUpdate.AcceptRisks are unsafe and inverted:
preserve the original bk (already DeepCopied), and when restoring set
cv.Spec.DesiredUpdate = bk; after the update, avoid dereferencing when
cv.Spec.DesiredUpdate is nil by checking for nil first and then assert absence
of the previously-added risks (use BeFalse for slices.Contains for riskA/riskB)
or, if bk was non-nil, assert that the restored AcceptRisks equals the original
bk.AcceptRisks (or contains the same names); reference bk, cv.Spec.DesiredUpdate
and AcceptRisks when making the nil-safe checks and flipping the assertions in
the "Checking that new risks can be removed" section.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test/util/util.go`:
- Around line 31-39: The function IsTechPreviewNoUpgrade can dereference a nil
featureGate if c.FeatureGates().Get returns a non-NotFound error because
o.Expect(err).NotTo(...) may not stop execution; after the
o.Expect(err).NotTo(o.HaveOccurred(), ...) call in the error branch, add an
explicit return (e.g., return false) to avoid continuing and dereferencing
featureGate, ensuring the function exits safely when an unexpected error occurs.

Comment on lines +31 to +39
func IsTechPreviewNoUpgrade(c *configclientv1.ConfigV1Client) bool {
featureGate, err := c.FeatureGates().Get(context.Background(), "cluster", metav1.GetOptions{})
if err != nil {
if kapierrs.IsNotFound(err) {
return false
}
o.Expect(err).NotTo(o.HaveOccurred(), "could not retrieve feature-gate: %v", err)
}
return featureGate.Spec.FeatureSet == configv1.TechPreviewNoUpgrade
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid potential nil dereference after a non-NotFound error

o.Expect(err) may not abort execution in all test setups, so featureGate can still be nil and the dereference on Line 39 can panic. Add a return after the expectation to keep this safe.

🔧 Suggested fix
func IsTechPreviewNoUpgrade(c *configclientv1.ConfigV1Client) bool {
	featureGate, err := c.FeatureGates().Get(context.Background(), "cluster", metav1.GetOptions{})
	if err != nil {
		if kapierrs.IsNotFound(err) {
			return false
		}
		o.Expect(err).NotTo(o.HaveOccurred(), "could not retrieve feature-gate: %v", err)
+		return false
	}
	return featureGate.Spec.FeatureSet == configv1.TechPreviewNoUpgrade
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func IsTechPreviewNoUpgrade(c *configclientv1.ConfigV1Client) bool {
featureGate, err := c.FeatureGates().Get(context.Background(), "cluster", metav1.GetOptions{})
if err != nil {
if kapierrs.IsNotFound(err) {
return false
}
o.Expect(err).NotTo(o.HaveOccurred(), "could not retrieve feature-gate: %v", err)
}
return featureGate.Spec.FeatureSet == configv1.TechPreviewNoUpgrade
func IsTechPreviewNoUpgrade(c *configclientv1.ConfigV1Client) bool {
featureGate, err := c.FeatureGates().Get(context.Background(), "cluster", metav1.GetOptions{})
if err != nil {
if kapierrs.IsNotFound(err) {
return false
}
o.Expect(err).NotTo(o.HaveOccurred(), "could not retrieve feature-gate: %v", err)
return false
}
return featureGate.Spec.FeatureSet == configv1.TechPreviewNoUpgrade
}
🤖 Prompt for AI Agents
In `@test/util/util.go` around lines 31 - 39, The function IsTechPreviewNoUpgrade
can dereference a nil featureGate if c.FeatureGates().Get returns a non-NotFound
error because o.Expect(err).NotTo(...) may not stop execution; after the
o.Expect(err).NotTo(o.HaveOccurred(), ...) call in the error branch, add an
explicit return (e.g., return false) to avoid continuing and dereferencing
featureGate, ensuring the function exits safely when an unexpected error occurs.

@hongkailiu
Copy link
Member Author

/retest requied

@hongkailiu
Copy link
Member Author

/retest-required

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jan 26, 2026

@hongkailiu: The /retest command does not accept any targets.
The following commands are available to trigger required jobs:

/test e2e-agnostic-operator
/test e2e-agnostic-operator-techpreview
/test e2e-agnostic-ovn
/test e2e-agnostic-ovn-upgrade-into-change
/test e2e-agnostic-ovn-upgrade-into-change-techpreview
/test e2e-agnostic-ovn-upgrade-out-of-change
/test e2e-agnostic-ovn-upgrade-out-of-change-techpreview
/test e2e-aws-ovn-techpreview
/test e2e-hypershift
/test e2e-hypershift-conformance
/test gofmt
/test images
/test lint
/test okd-scos-images
/test unit
/test verify-deps
/test verify-update
/test verify-yaml

The following commands are available to trigger optional jobs:

/test e2e-agnostic-operator-devpreview
/test e2e-extended-tests
/test okd-scos-e2e-aws-ovn

Use /test all to run the following jobs that were automatically triggered:

pull-ci-openshift-cluster-version-operator-main-e2e-agnostic-operator
pull-ci-openshift-cluster-version-operator-main-e2e-agnostic-ovn
pull-ci-openshift-cluster-version-operator-main-e2e-agnostic-ovn-upgrade-into-change
pull-ci-openshift-cluster-version-operator-main-e2e-agnostic-ovn-upgrade-out-of-change
pull-ci-openshift-cluster-version-operator-main-e2e-aws-ovn-techpreview
pull-ci-openshift-cluster-version-operator-main-e2e-hypershift
pull-ci-openshift-cluster-version-operator-main-e2e-hypershift-conformance
pull-ci-openshift-cluster-version-operator-main-gofmt
pull-ci-openshift-cluster-version-operator-main-images
pull-ci-openshift-cluster-version-operator-main-lint
pull-ci-openshift-cluster-version-operator-main-okd-scos-images
pull-ci-openshift-cluster-version-operator-main-unit
pull-ci-openshift-cluster-version-operator-main-verify-deps
pull-ci-openshift-cluster-version-operator-main-verify-update
Details

In response to this:

/retest requied

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jan 26, 2026

@hongkailiu: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-hypershift 85e0753 link true /test e2e-hypershift
ci/prow/e2e-aws-ovn-techpreview 85e0753 link true /test e2e-aws-ovn-techpreview

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants