Skip to content

CNTRLPLANE-3022: Add osImageStream to NodePool spec and status#8675

Open
sdminonne wants to merge 1 commit into
openshift:mainfrom
sdminonne:CNTRLPLANE-3022/Dual-Stream-NodePool-API-add-osImageStream-to-spec-status
Open

CNTRLPLANE-3022: Add osImageStream to NodePool spec and status#8675
sdminonne wants to merge 1 commit into
openshift:mainfrom
sdminonne:CNTRLPLANE-3022/Dual-Stream-NodePool-API-add-osImageStream-to-spec-status

Conversation

@sdminonne
Copy link
Copy Markdown
Contributor

@sdminonne sdminonne commented Jun 4, 2026

What this PR does / why we need it:

Phase 0 of the Dual-Stream RHEL 9/10 NodePool enhancement (OCPSTRAT-3014 / CNTRLPLANE-3022). Adds osImageStream fields to NodePool spec and status, gated behind the OSStreams feature gate (TechPreview).

  • New type OSImageStreamReference with name field restricted to rhel-9 or rhel-10 via Enum validation
  • NodePoolSpec.osImageStream — allows users to override the default OS images for a NodePool by referencing a known OSImageStream. A field-level CEL rule prevents downgrades from rhel-10 to rhel-9
  • NodePoolStatus.osImageStream — reports the observed OS stream on nodes in the pool
  • OSStreams feature gate registered in all 4 feature gate files (disabled in Default, enabled in TechPreview)
  • Envtest CEL validation tests covering enum validation, downgrade prevention, and upgrade/add scenarios

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3022

Special notes for your reviewer:

The CEL downgrade-prevention rule is placed at the field level (on osImageStream) rather than the struct level (on NodePoolSpec). This ensures the rule is co-gated with the field itself and avoids CRD installation failures in the Default variant where the field doesn't exist in the schema.

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Test plan

  • make api — CRDs regenerated correctly (field present only in TechPreview/CustomNoUpgrade)
  • make update — Full dependency sync passed
  • make verify — Linting and generation passed
  • make test-envtest-ocp — All envtests passed across K8s 1.30–1.35
  • make test — All unit tests passed
  • make run-gitlint — Commit message validated

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Node pools can now opt into an OS image stream to override default OS images and select supported RHEL streams; built-in validation prevents unsupported downgrades.
  • Chores

    • Updated default feature gate settings to include the new OSStreams gate (enabled for TechPreview variants, added/disabled in relevant defaults).

@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 4, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jun 4, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Jun 4, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented Jun 4, 2026

@sdminonne: This pull request references CNTRLPLANE-3022 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 "5.0.0" version, but no target version was set.

Details

In response to this:

What this PR does / why we need it:

Phase 0 of the Dual-Stream RHEL 9/10 NodePool enhancement (OCPSTRAT-3014 / CNTRLPLANE-3022). Adds osImageStream fields to NodePool spec and status, gated behind the OSStreams feature gate (TechPreview).

  • New type OSImageStreamReference with name field restricted to rhel-9 or rhel-10 via Enum validation
  • NodePoolSpec.osImageStream — allows users to override the default OS images for a NodePool by referencing a known OSImageStream. A field-level CEL rule prevents downgrades from rhel-10 to rhel-9
  • NodePoolStatus.osImageStream — reports the observed OS stream on nodes in the pool
  • OSStreams feature gate registered in all 4 feature gate files (disabled in Default, enabled in TechPreview)
  • Envtest CEL validation tests covering enum validation, downgrade prevention, and upgrade/add scenarios

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3022

Special notes for your reviewer:

The CEL downgrade-prevention rule is placed at the field level (on osImageStream) rather than the struct level (on NodePoolSpec). This ensures the rule is co-gated with the field itself and avoids CRD installation failures in the Default variant where the field doesn't exist in the schema.

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Test plan

  • make api — CRDs regenerated correctly (field present only in TechPreview/CustomNoUpgrade)
  • make update — Full dependency sync passed
  • make verify — Linting and generation passed
  • make test-envtest-ocp — All envtests passed across K8s 1.30–1.35
  • make test — All unit tests passed
  • make run-gitlint — Commit message validated

🤖 Generated with Claude Code

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
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: d6af6489-97e4-4ee4-8391-098d7516f655

📥 Commits

Reviewing files that changed from the base of the PR and between 0b89acd and ee12e05.

⛔ Files ignored due to path filters (19)
  • api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !**/zz_generated*.go, !**/zz_generated*
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/zz_generated*
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/nodepools.hypershift.openshift.io/OSStreams.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • client/applyconfiguration/hypershift/v1beta1/nodepoolspec.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/nodepoolstatus.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/osimagestreamreference.go is excluded by !client/**
  • client/applyconfiguration/utils.go is excluded by !client/**
  • cmd/install/assets/crds/hypershift-operator/payload-manifests/featuregates/featureGate-Hypershift-Default.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/payload-manifests/featuregates/featureGate-Hypershift-TechPreviewNoUpgrade.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/payload-manifests/featuregates/featureGate-SelfManagedHA-Default.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/payload-manifests/featuregates/featureGate-SelfManagedHA-TechPreviewNoUpgrade.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/tests/nodepools.hypershift.openshift.io/featuregated.nodepools.osimagestream.testsuite.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/nodepools-CustomNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/nodepools-TechPreviewNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • docs/content/reference/aggregated-docs.md is excluded by !docs/content/reference/aggregated-docs.md
  • docs/content/reference/api.md is excluded by !docs/content/reference/api.md
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/nodepool_types.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !vendor/**, !**/vendor/**, !**/zz_generated*.go, !**/zz_generated*
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests.yaml is excluded by !vendor/**, !**/vendor/**, !**/zz_generated*
📒 Files selected for processing (5)
  • api/hypershift/v1beta1/featuregates/featureGate-Hypershift-Default.yaml
  • api/hypershift/v1beta1/featuregates/featureGate-Hypershift-TechPreviewNoUpgrade.yaml
  • api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-Default.yaml
  • api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-TechPreviewNoUpgrade.yaml
  • api/hypershift/v1beta1/nodepool_types.go
✅ Files skipped from review due to trivial changes (1)
  • api/hypershift/v1beta1/featuregates/featureGate-Hypershift-Default.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-TechPreviewNoUpgrade.yaml
  • api/hypershift/v1beta1/featuregates/featureGate-Hypershift-TechPreviewNoUpgrade.yaml
  • api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-Default.yaml
  • api/hypershift/v1beta1/nodepool_types.go

📝 Walkthrough

Walkthrough

This pull request adds support for OS image stream selection in HyperShift node pools. The change introduces a new OSStreams feature gate and associated API fields to NodePoolSpec and NodePoolStatus. A new OSImageStreamReference type allows specifying either rhel-9 or rhel-10 as the target OS stream. The implementation includes CEL validation to prevent downgrading from rhel-10 to rhel-9. The feature gate is enabled across four configuration profiles: Hypershift Default, Hypershift TechPreviewNoUpgrade, SelfManagedHA Default, and SelfManagedHA TechPreviewNoUpgrade.

Sequence Diagram(s)

sequenceDiagram
  participant ClusterAdmin
  participant FeatureGateConfig
  participant NodePoolAPI
  ClusterAdmin->>FeatureGateConfig: add/enable "OSStreams" entry (profiles)
  ClusterAdmin->>NodePoolAPI: create/update NodePool with osImageStream
  NodePoolAPI->>FeatureGateConfig: read "OSStreams" feature gate
  Note right of NodePoolAPI: apply CEL validation (prevent rhel-10 -> rhel-9)
  NodePoolAPI-->>ClusterAdmin: NodePoolStatus.osImageStream observed or defaulted
Loading
🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test Structure And Quality ⚠️ Warning Tests lack meaningful assertion messages on 59% of Expect statements (23/39 in generator.go, 9/11 in suite_test.go), making diagnosis difficult when failures occur, violating requirement 4. Add meaningful failure messages to all Expect assertions. For example, change Expect(err).ToNot(HaveOccurred()) to Expect(err).ToNot(HaveOccurred(), "failed to create test object") for all bare assertions.
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and accurately summarizes the main change: adding an osImageStream field to NodePool spec and status, with the ticket reference providing context.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed All test names in the new test suite are stable and deterministic. The YAML file defines static test names without dynamic identifiers, and hardcoded feature set strings are used in fmt.Sprintf calls.
Topology-Aware Scheduling Compatibility ✅ Passed PR adds CRD type definitions and feature gate registrations only. No deployment manifests, operator code, pod specs, or scheduling constraints introduced.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests found. PR modifies only YAML config files and Go type definitions, making the IPv6/disconnected network test check inapplicable.
No-Weak-Crypto ✅ Passed PR contains only YAML feature gate configurations and Go API type definitions with no weak crypto algorithms, custom implementations, or insecure comparisons.
Container-Privileges ✅ Passed PR contains only FeatureGate configuration files and Go API type definitions with no container manifests or privileged container configurations.
No-Sensitive-Data-In-Logs ✅ Passed PR adds osImageStream field to NodePool with enum values (rhel-9, rhel-10). No logging code added; field contains non-sensitive operational metadata, not secrets or PII.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci openshift-ci Bot added area/api Indicates the PR includes changes for the API area/cli Indicates the PR includes changes for CLI area/documentation Indicates the PR includes changes for documentation and removed do-not-merge/needs-area labels Jun 4, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Jun 4, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: sdminonne
Once this PR has been reviewed and has the lgtm label, please assign csrwng for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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

@github-actions github-actions Bot temporarily deployed to docs-preview/pr-8675 June 4, 2026 21:23 Inactive
Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/hypershift/v1beta1/nodepool_types.go`:
- Around line 242-261: The CEL XValidation currently allows removing
osImageStream (self.name unset) to bypass the rhel-10 -> rhel-9 downgrade block;
update the validation expression on the OSImageStream field (OSImageStream in
nodepool_types.go) to require that when oldSelf.name == 'rhel-10' the new
self.name must exist and not equal 'rhel-9' (e.g. replace the rule with:
"!has(oldSelf.name) || oldSelf.name != 'rhel-10' || (has(self.name) && self.name
!= 'rhel-9')") so an unset self.name no longer permits a downgrade escape hatch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 38e6fd98-eec7-43ca-b03a-e9f00daa8428

📥 Commits

Reviewing files that changed from the base of the PR and between 38f7b17 and 0b89acd.

⛔ Files ignored due to path filters (19)
  • api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !**/zz_generated*.go, !**/zz_generated*
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/zz_generated*
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/nodepools.hypershift.openshift.io/OSStreams.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • client/applyconfiguration/hypershift/v1beta1/nodepoolspec.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/nodepoolstatus.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/osimagestreamreference.go is excluded by !client/**
  • client/applyconfiguration/utils.go is excluded by !client/**
  • cmd/install/assets/crds/hypershift-operator/payload-manifests/featuregates/featureGate-Hypershift-Default.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/payload-manifests/featuregates/featureGate-Hypershift-TechPreviewNoUpgrade.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/payload-manifests/featuregates/featureGate-SelfManagedHA-Default.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/payload-manifests/featuregates/featureGate-SelfManagedHA-TechPreviewNoUpgrade.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/tests/nodepools.hypershift.openshift.io/featuregated.nodepools.osimagestream.testsuite.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/nodepools-CustomNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/nodepools-TechPreviewNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • docs/content/reference/aggregated-docs.md is excluded by !docs/content/reference/aggregated-docs.md
  • docs/content/reference/api.md is excluded by !docs/content/reference/api.md
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/nodepool_types.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !vendor/**, !**/vendor/**, !**/zz_generated*.go, !**/zz_generated*
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests.yaml is excluded by !vendor/**, !**/vendor/**, !**/zz_generated*
📒 Files selected for processing (5)
  • api/hypershift/v1beta1/featuregates/featureGate-Hypershift-Default.yaml
  • api/hypershift/v1beta1/featuregates/featureGate-Hypershift-TechPreviewNoUpgrade.yaml
  • api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-Default.yaml
  • api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-TechPreviewNoUpgrade.yaml
  • api/hypershift/v1beta1/nodepool_types.go

Comment on lines +242 to +261
// osImageStream specifies an OS stream to be used for nodes in this pool.
//
// This field can be optionally set to a known OSImageStream name to change
// the OS and Extension images with a well-known, tested, release-provided
// set of images. This enables a streamlined way of switching the pool's
// node OS to a different version than the cluster default, such as
// transitioning to a major RHEL version.
//
// When set, the referenced stream overrides the default OS images for the
// pool. When omitted, the pool uses the release version's default stream
// (rhel-9 for OCP < 5.0, rhel-10 for OCP >= 5.0).
// Changing this field triggers a rollout. Forward transitions
// (rhel-9 -> rhel-10) are allowed; backward transitions
// (rhel-10 -> rhel-9) are rejected by CEL validation because
// in-place OS downgrades are not supported.
//
// +openshift:enable:FeatureGate=OSStreams
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.name) || oldSelf.name != 'rhel-10' || self.name != 'rhel-9'",message="OS stream downgrade from rhel-10 to rhel-9 is not allowed; create a new NodePool instead"
// +optional
OSImageStream OSImageStreamReference `json:"osImageStream,omitempty,omitzero"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether validation/tests currently cover unset transition from rhel-10.
rg -n -C3 'osImageStream|rhel-10|rhel-9|downgrade|unset|remove' \
  api/hypershift/v1beta1 \
  cmd/install/assets/crds/hypershift-operator/tests

Repository: openshift/hypershift

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focused checks for OSStreams/CEL coverage in the ratcheting testsuite assets
rg -n --max-count 50 'osImageStream' cmd/install/assets/crds/hypershift-operator/tests || true
rg -n --max-count 50 'rhel-10' cmd/install/assets/crds/hypershift-operator/tests || true
rg -n --max-count 50 'rhel-9' cmd/install/assets/crds/hypershift-operator/tests || true

# Also check the full repo for any OSStreams transition tests (still capped)
rg -n --max-count 50 'OS stream downgrade from rhel-10 to rhel-9' . || true
rg -n --max-count 50 'OSStreams' cmd/install/assets/crds/hypershift-operator/tests || true

Repository: openshift/hypershift

Length of output: 7555


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f="cmd/install/assets/crds/hypershift-operator/tests/nodepools.hypershift.openshift.io/featuregated.nodepools.osimagestream.testsuite.yaml"

# Show the whole test file with line numbers (it should be small)
wc -l "$f"
cat -n "$f" | sed -n '1,260p'

# Focused searches for omit/unset/delete-like test cases
rg -n 'unset|null|remove|omitted|delete|no osImageStream|without osImageStream|osImageStream:\s*$' "$f" || true

Repository: openshift/hypershift

Length of output: 8676


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f="cmd/install/assets/crds/hypershift-operator/tests/nodepools.hypershift.openshift.io/featuregated.nodepools.osimagestream.testsuite.yaml"

# Tail the bottom part to see any additional onUpdate cases
tail -n 120 "$f" | nl -ba | sed -n '1,130p'

# Search for any removal/unset-style update tests
rg -n 'remove|unset|null|omitted|without osImageStream|no osImageStream|osImageStream:\s*$' "$f" || true

Repository: openshift/hypershift

Length of output: 107


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f="cmd/install/assets/crds/hypershift-operator/tests/nodepools.hypershift.openshift.io/featuregated.nodepools.osimagestream.testsuite.yaml"

# Tail and add line numbers via awk
tail -n 120 "$f" | awk '{printf "%5d\t%s\n", NR, $0}'

# Search for removal/unset-style update tests (tests earlier showed only rhel-10<->rhel-9)
rg -n 'remove|unset|null|omitted|without osImageStream|no osImageStream|osImageStream:\s*$' "$f" || true

Repository: openshift/hypershift

Length of output: 4374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f="api/hypershift/v1beta1/nodepool_types.go"
sed -n '220,290p' "$f" | nl -ba | sed -n '1,110p'

Repository: openshift/hypershift

Length of output: 107


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f="api/hypershift/v1beta1/nodepool_types.go"
awk 'NR>=220 && NR<=290 {printf "%5d\t%s\n", NR, $0}' "$f"

Repository: openshift/hypershift

Length of output: 3831


🌐 Web query:

Kubernetes CEL behavior when accessing missing field (self.name) evaluates to null; comparison != 'string'

💡 Result:

In Kubernetes’ CEL (x-kubernetes-validations / CEL admission rules), if self.name is a missing field, CEL field selection returns the default value for the field’s type; for message-typed fields that default is null (null_type) rather than throwing. The CEL language spec also defines that null has its own type (null_type) and that type checking/comparison behavior is sensitive to the operand types. So for the specific pattern “comparison!= 'string'”: - If self.name is missing and the schema says name is a string field (not message), field selection produces the string type’s primitive default (empty string), not null, so self.name!= 'string' evaluates based on empty-string semantics. - If self.name is missing but name is modeled as a message (or wrapper/nullable message) where the missing default is null, then self.name evaluates to null_type and the comparison self.name!= 'string' is a heterogeneous comparison against null_type; CEL generally doesn’t treat null as a normal string value. In K8s/CEL, the safe/expected way to handle “missing vs present” is to guard with has(self.name) (and/or use the CEL optional field-selection operator such as e.?f for optional presence). Practical takeaway: don’t rely on “!= 'string'” to distinguish missing from explicit values. Use has(self.name) and then compare, e.g. (has(self.name)? self.name!= 'string': false) Grounding: - CEL field selection semantics: if a message field is declared but not set, CEL produces the field’s default value; for message types this default is null [1][2]. - CEL null value semantics: null has its own type (null_type) [3][2]. - Kubernetes CEL guidance: use has to safely check field existence in validations (optional/absent fields otherwise cause errors or mis-evaluation) [4][5]. - Kubernetes/CEL discussion indicates that changing presence/default handling can affect comparisons involving null [6].

Citations:


Close the osImageStream unset downgrade escape hatch in the OSStreams CEL rule.

The current rule blocks explicit rhel-10 -> rhel-9, but it doesn’t block rhel-10 -> unset (missing self.name), which can satisfy self.name != 'rhel-9' and effectively bypass the downgrade protection. The existing tests only cover rhel-10 -> rhel-9 failure and don’t include an osImageStream removal/unset update case.

💡 Proposed CEL fix
-	// +kubebuilder:validation:XValidation:rule="!has(oldSelf.name) || oldSelf.name != 'rhel-10' || self.name != 'rhel-9'",message="OS stream downgrade from rhel-10 to rhel-9 is not allowed; create a new NodePool instead"
+	// +kubebuilder:validation:XValidation:rule="!has(oldSelf.name) || oldSelf.name != 'rhel-10' || (has(self.name) && self.name != 'rhel-9')",message="OS stream downgrade from rhel-10 to rhel-9 is not allowed; create a new NodePool instead"
📝 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
// osImageStream specifies an OS stream to be used for nodes in this pool.
//
// This field can be optionally set to a known OSImageStream name to change
// the OS and Extension images with a well-known, tested, release-provided
// set of images. This enables a streamlined way of switching the pool's
// node OS to a different version than the cluster default, such as
// transitioning to a major RHEL version.
//
// When set, the referenced stream overrides the default OS images for the
// pool. When omitted, the pool uses the release version's default stream
// (rhel-9 for OCP < 5.0, rhel-10 for OCP >= 5.0).
// Changing this field triggers a rollout. Forward transitions
// (rhel-9 -> rhel-10) are allowed; backward transitions
// (rhel-10 -> rhel-9) are rejected by CEL validation because
// in-place OS downgrades are not supported.
//
// +openshift:enable:FeatureGate=OSStreams
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.name) || oldSelf.name != 'rhel-10' || self.name != 'rhel-9'",message="OS stream downgrade from rhel-10 to rhel-9 is not allowed; create a new NodePool instead"
// +optional
OSImageStream OSImageStreamReference `json:"osImageStream,omitempty,omitzero"`
// osImageStream specifies an OS stream to be used for nodes in this pool.
//
// This field can be optionally set to a known OSImageStream name to change
// the OS and Extension images with a well-known, tested, release-provided
// set of images. This enables a streamlined way of switching the pool's
// node OS to a different version than the cluster default, such as
// transitioning to a major RHEL version.
//
// When set, the referenced stream overrides the default OS images for the
// pool. When omitted, the pool uses the release version's default stream
// (rhel-9 for OCP < 5.0, rhel-10 for OCP >= 5.0).
// Changing this field triggers a rollout. Forward transitions
// (rhel-9 -> rhel-10) are allowed; backward transitions
// (rhel-10 -> rhel-9) are rejected by CEL validation because
// in-place OS downgrades are not supported.
//
// +openshift:enable:FeatureGate=OSStreams
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.name) || oldSelf.name != 'rhel-10' || (has(self.name) && self.name != 'rhel-9')",message="OS stream downgrade from rhel-10 to rhel-9 is not allowed; create a new NodePool instead"
// +optional
OSImageStream OSImageStreamReference `json:"osImageStream,omitempty,omitzero"`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/hypershift/v1beta1/nodepool_types.go` around lines 242 - 261, The CEL
XValidation currently allows removing osImageStream (self.name unset) to bypass
the rhel-10 -> rhel-9 downgrade block; update the validation expression on the
OSImageStream field (OSImageStream in nodepool_types.go) to require that when
oldSelf.name == 'rhel-10' the new self.name must exist and not equal 'rhel-9'
(e.g. replace the rule with: "!has(oldSelf.name) || oldSelf.name != 'rhel-10' ||
(has(self.name) && self.name != 'rhel-9')") so an unset self.name no longer
permits a downgrade escape hatch.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 41.43%. Comparing base (f13c62d) to head (ee12e05).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8675   +/-   ##
=======================================
  Coverage   41.43%   41.43%           
=======================================
  Files         756      756           
  Lines       93647    93647           
=======================================
  Hits        38802    38802           
  Misses      52124    52124           
  Partials     2721     2721           
Flag Coverage Δ
cmd-support 34.87% <ø> (ø)
cpo-hostedcontrolplane 43.50% <ø> (ø)
cpo-other 42.74% <ø> (ø)
hypershift-operator 51.57% <ø> (ø)
other 31.64% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hypershift-jira-solve-ci
Copy link
Copy Markdown

hypershift-jira-solve-ci Bot commented Jun 4, 2026

Test Failure Analysis Complete

Job Information

  • Prow Job: codecov/project (Codecov coverage gate, not a Prow CI job)
  • Build ID: Check Run 79618384841
  • PR: #8675CNTRLPLANE-3022: Add osImageStream to NodePool spec and status
  • Result: failure — project coverage 41.43% (−0.01%) compared to base e25a87a

Test Failure Analysis

Error

codecov/project: 41.43% (-0.01%) compared to e25a87a
Project coverage decreased by 0.01% (41.44% → 41.43%).
Report is 12 commits behind head on main.

Summary

The codecov/project check failed because overall project coverage dropped by 0.01% (from 41.44% to 41.43%). This is not caused by the PR's own changescodecov/patch passed with "Coverage not affected" and "All modified and coverable lines are covered by tests." The PR only adds Go struct/type definitions and comments to nodepool_types.go, which contain no executable code. The coverage drop comes from 4 files with indirect coverage changes — unrelated files whose coverage shifted between the stale base commit (e25a87a, 12 commits behind main HEAD) and the PR head. This is a false positive caused by a stale base comparison, and codecov/project is not a required merge check (PR #8484 was previously merged despite the same failure).

Root Cause

The failure is a stale-base false positive in Codecov's project-level coverage gate. The specific chain:

  1. PR changes are coverage-neutral: The only non-generated Go file modified is api/hypershift/v1beta1/nodepool_types.go, with +37 lines of pure struct definitions (OSImageStreamReference, NodePoolSpec.OSImageStream field), type declarations, CEL validation annotations, and comments. These contain zero executable code paths — Codecov confirms this via the passing codecov/patch check.

  2. Base commit is stale: The Codecov report states it is "12 commits behind head on main." The comparison base e25a87a had 41.44% coverage, but main has since settled to ~41.43% (visible in PR CNTRLPLANE-3271: add External OIDC e2e tests for v2 framework #8674 which shows +0.00% compared to 38f7b17 at 41.43%, and PR CNTRLPLANE-3020: Adopt coreos/stream-metadata-go upstream library #8673 which passed despite -0.02% drop because the "adjusted base" was 41.43%).

  3. Indirect coverage changes cause the drop: The diff shows −5 hits and +10 misses across 93,647 lines in 4 unrelated files. These are coverage fluctuations from commits merged to main between e25a87a and the PR's HEAD — not from this PR's changes.

  4. No coverage: threshold configured in codecov.yml: The repo's codecov.yml has no coverage: section, so Codecov uses its default behavior: the project check fails on any decrease. With no configured threshold tolerance (e.g., threshold: 0.05%), even a 0.01% drop from a stale base triggers failure.

  5. Not a merge blocker: The codecov/project check is not a required status check for merging. PR OCPBUGS-86008: Gate Route watch on management cluster capability #8484 was previously merged with a codecov/project failure, confirming this is informational only.

Recommendations
  1. This failure can be safely ignored for merging — it is not a required check, and the PR's own code introduces zero coverage change (confirmed by the passing codecov/patch).

  2. Rebase the PR onto current main to pick up the latest base commit for Codecov comparison. This will likely resolve the stale-base comparison issue, as main is currently at ~41.43% and the PR is at 41.43%.

  3. Consider adding a coverage threshold to codecov.yml to prevent future false positives from stale-base comparisons:

    coverage:
      status:
        project:
          default:
            threshold: 0.05%

    This would allow up to 0.05% coverage fluctuation before failing, which prevents noise from indirect coverage changes.

Evidence
Evidence Detail
codecov/project result failure — 41.43% (−0.01%) compared to base e25a87a
codecov/patch result success — "Coverage not affected when comparing e25a87a...0b89acd"
All modified lines covered "All modified and coverable lines are covered by tests"
Stale base "Report is 12 commits behind head on main"
Coverage diff −5 hits, +10 misses, +3 lines (93,644 → 93,647) across 4 indirect files
PR file changes Only nodepool_types.go modified (struct/type defs, no executable code)
No threshold configured codecov.yml has no coverage: section — uses default (fail on any decrease)
Not a merge blocker PR #8484 merged with same codecov/project failure
PR #8673 comparison Passed at −0.02% because "adjusted base" was 41.43% — same as this PR's coverage

Add OSImageStreamReference type and osImageStream fields to NodePoolSpec
and NodePoolStatus, gated behind the OSStreams feature gate
(TechPreview). This is Phase 0 of the Dual-Stream RHEL 9/10 NodePool
enhancement, enabling per-pool OS stream selection in HyperShift.

The spec field allows users to override the default OS images for a
NodePool by referencing a known OSImageStream (rhel-9 or rhel-10).
A field-level CEL validation rule prevents downgrades from rhel-10
to rhel-9. The status field reports the observed OS stream on nodes.

Signed-off-by: Salvatore Dario Minonne <sminonne@redhat.com>
Commit-Message-Assisted-by: Claude (via Claude Code)
@sdminonne sdminonne force-pushed the CNTRLPLANE-3022/Dual-Stream-NodePool-API-add-osImageStream-to-spec-status branch from 0b89acd to ee12e05 Compare June 5, 2026 10:15
@sdminonne sdminonne marked this pull request as ready for review June 5, 2026 10:16
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 5, 2026
@openshift-ci openshift-ci Bot requested review from devguyio and muraee June 5, 2026 10:17
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Jun 5, 2026

@sdminonne: all tests passed!

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

area/api Indicates the PR includes changes for the API area/cli Indicates the PR includes changes for CLI area/documentation Indicates the PR includes changes for documentation 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.

2 participants