Skip to content

DNM: CNTRLPLANE-3612: Add dual-stream RHEL 9/10 NodePool support#8714

Closed
hypershift-jira-solve-ci[bot] wants to merge 5 commits into
openshift:mainfrom
hypershift-community:fix-CNTRLPLANE-3612
Closed

DNM: CNTRLPLANE-3612: Add dual-stream RHEL 9/10 NodePool support#8714
hypershift-jira-solve-ci[bot] wants to merge 5 commits into
openshift:mainfrom
hypershift-community:fix-CNTRLPLANE-3612

Conversation

@hypershift-jira-solve-ci

@hypershift-jira-solve-ci hypershift-jira-solve-ci Bot commented Jun 10, 2026

Copy link
Copy Markdown

What this PR does / why we need it:

Implements dual-stream RHEL 9/10 NodePool support for OpenShift 5.0+ payloads.

Starting with OpenShift 5.0, release payloads carry both RHEL 9 and RHEL 10 boot images. This PR threads the OS stream selection end-to-end: from the NodePool spec through the controller, token secret, ignition server, and into MCC bootstrap manifests.

Key changes:

  • Boot image metadata parsing (support/releaseinfo): DeserializeImageMetadata now parses both the legacy single-stream "stream" key and the new multi-stream "streams" key from the boot image ConfigMap. A StreamMetadataForStream helper resolves stream-specific metadata with fallback to legacy.

  • RHEL stream resolution (nodepool/rhel_stream.go): Pure function getRHELStream() resolves the target RHEL stream based on spec.osImageStream.name, release version, and runc usage. RHEL 10 is the default for >= 5.0; runc forces RHEL 9 (RHEL 10 does not ship runc). Pre-5.0 releases use legacy single-stream behavior.

  • Controller plumbing (nodepool/): The resolved stream flows into rolloutConfig for hash computation (only explicit stream changes trigger rollouts), into defaultNodePoolAMI/defaultNodePoolGCPImage for stream-aware boot image selection, and into the token secret as os-stream for the ignition server.

  • Ignition server (ignition-server/): GetPayload() accepts the OS stream and writes an OSImageStream CR (99_osimagestream.yaml) to the MCC manifest directory, telling MCC bootstrap which RHEL stream to use when rendering MachineConfigs.

  • Status reporting (nodepool/version.go): status.osImageStream is populated by detecting the RHEL stream from CAPI Machine NodeInfo.OSImage (RHCOS 4xx = RHEL 9, 5xx = RHEL 10).

  • Validation conditions: rhel-10 on release < 5.0 and rhel-10 + runc are rejected with clear condition messages.

Which issue(s) this PR fixes:

Fixes https://issues.redhat.com/browse/CNTRLPLANE-3612

Special notes for your reviewer:

  • The rhelStream field is included in rolloutConfig.Hash() but only populated from the explicit spec.osImageStream.name (not the resolved default). This avoids fleet-wide rollouts when upgrading to 5.0 — existing NodePools silently pick up the default stream without a rollout.
  • RHEL 10 does not ship runc. When a NodePool uses ContainerRuntimeConfig with defaultRuntime=runc, the controller automatically falls back to RHEL 9 even on >= 5.0 releases.
  • Boot image ConfigMap parsing sorts map keys deterministically when the legacy "stream" key is absent, ensuring stable behavior.

Checklist:

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

Always review AI generated responses prior to use.
Generated with Claude Code via /jira:solve [CNTRLPLANE-3612](https://redhat.atlassian.net/browse/CNTRLPLANE-3612)

Summary by CodeRabbit

  • New Features

    • Added support for explicit RHEL stream selection (9 or 10) in NodePool configurations.
    • Implemented automatic detection and tracking of OS image stream from cluster machines.
    • Added multi-stream release image metadata support.
  • Bug Fixes

    • Corrected Windows AMI metadata resolution path.
    • Fixed Azure Marketplace metadata extraction.
  • Improvements

    • Added validation for RHEL stream compatibility with release versions.
    • Enhanced ignition payload generation to include OS stream information.

@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-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jun 10, 2026
@openshift-ci-robot

openshift-ci-robot commented Jun 10, 2026

Copy link
Copy Markdown

@hypershift-jira-solve-ci[bot]: This pull request references CNTRLPLANE-3612 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:

Implements dual-stream RHEL 9/10 NodePool support for OpenShift 5.0+ payloads.

Starting with OpenShift 5.0, release payloads carry both RHEL 9 and RHEL 10 boot images. This PR threads the OS stream selection end-to-end: from the NodePool spec through the controller, token secret, ignition server, and into MCC bootstrap manifests.

Key changes:

  • Boot image metadata parsing (support/releaseinfo): DeserializeImageMetadata now parses both the legacy single-stream "stream" key and the new multi-stream "streams" key from the boot image ConfigMap. A StreamMetadataForStream helper resolves stream-specific metadata with fallback to legacy.

  • RHEL stream resolution (nodepool/rhel_stream.go): Pure function getRHELStream() resolves the target RHEL stream based on spec.osImageStream.name, release version, and runc usage. RHEL 10 is the default for >= 5.0; runc forces RHEL 9 (RHEL 10 does not ship runc). Pre-5.0 releases use legacy single-stream behavior.

  • Controller plumbing (nodepool/): The resolved stream flows into rolloutConfig for hash computation (only explicit stream changes trigger rollouts), into defaultNodePoolAMI/defaultNodePoolGCPImage for stream-aware boot image selection, and into the token secret as os-stream for the ignition server.

  • Ignition server (ignition-server/): GetPayload() accepts the OS stream and writes an OSImageStream CR (99_osimagestream.yaml) to the MCC manifest directory, telling MCC bootstrap which RHEL stream to use when rendering MachineConfigs.

  • Status reporting (nodepool/version.go): status.osImageStream is populated by detecting the RHEL stream from CAPI Machine NodeInfo.OSImage (RHCOS 4xx = RHEL 9, 5xx = RHEL 10).

  • Validation conditions: rhel-10 on release < 5.0 and rhel-10 + runc are rejected with clear condition messages.

Which issue(s) this PR fixes:

Fixes https://issues.redhat.com/browse/CNTRLPLANE-3612

Special notes for your reviewer:

  • The rhelStream field is included in rolloutConfig.Hash() but only populated from the explicit spec.osImageStream.name (not the resolved default). This avoids fleet-wide rollouts when upgrading to 5.0 — existing NodePools silently pick up the default stream without a rollout.
  • RHEL 10 does not ship runc. When a NodePool uses ContainerRuntimeConfig with defaultRuntime=runc, the controller automatically falls back to RHEL 9 even on >= 5.0 releases.
  • Boot image ConfigMap parsing sorts map keys deterministically when the legacy "stream" key is absent, ensuring stable behavior.

Checklist:

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

Always review AI generated responses prior to use.
Generated with Claude Code via /jira:solve [CNTRLPLANE-3612](https://redhat.atlassian.net/browse/CNTRLPLANE-3612)

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

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces multi-stream RHEL metadata support by replacing the external stream-metadata-go dependency with local CoreOS metadata types. The changes flow from foundational metadata model updates through RHEL stream resolution logic, into the NodePool controller pipeline, and ultimately to cloud provider image selection and ignition server payload generation. Key additions include RHEL stream resolver with version thresholds (5.0.0 for RHEL 10 eligibility), runc detection during config generation, machine-based stream inference via majority consensus, and token-based propagation of resolved stream information to ignition server manifests. All cloud providers (AWS, Azure, OpenStack, PowerVS, KubeVirt) are updated to use per-stream metadata selection via the new StreamMetadataForStream() API.

Possibly related PRs

  • openshift/hypershift#8673: Contains overlapping changes to getWindowsAMI Windows AMI metadata path in hypershift-operator/controllers/nodepool/aws.go.

Suggested reviewers

  • muraee
🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'DNM: CNTRLPLANE-3612: Add dual-stream RHEL 9/10 NodePool support' accurately summarizes the main feature being implemented across the changeset.
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 This PR contains no Ginkgo tests. All tests use standard Go testing.T with table-driven patterns. All test case names are static strings with no dynamic values, timestamps, UUIDs, pod names, or oth...
Test Structure And Quality ✅ Passed The custom check requires reviewing Ginkgo test code (Describe/It blocks). The PR adds no Ginkgo tests; it only adds standard Go tests using testing.T and Gomega matchers, which are not subject to...
Topology-Aware Scheduling Compatibility ✅ Passed This PR modifies controller logic and metadata handling for dual-stream RHEL image support; it does not introduce deployment manifests, pod affinity rules, topology spread constraints, nodeSelector...
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PR adds only standard Go unit tests (TestGetRHELStream, TestDetectRHELStreamFromOSImage, TestOsImageStreamFromMachines, TestWriteOSImageStreamManifest), not Ginkgo e2e tests. No IPv4 assumptions or...
No-Weak-Crypto ✅ Passed No weak cryptography detected: no MD5/SHA1/DES/RC4/Blowfish imports; no custom crypto; FNV-1a hash used appropriately; tokens use secure UUID generation.
Container-Privileges ✅ Passed PR introduces no container security issues; only metadata resource created (OSImageStream CR) with no privileged configurations, hostPID/Network/IPC, SYS_ADMIN, or allowPrivilegeEscalation settings.
No-Sensitive-Data-In-Logs ✅ Passed The PR logs non-sensitive enum values ("rhel-9", "rhel-10") representing RHEL OS stream selection. No passwords, tokens, API keys, PII, or sensitive data is exposed in logs.

✏️ 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 requested review from devguyio and muraee June 10, 2026 12:25
@openshift-ci openshift-ci Bot added area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/platform/aws PR/issue for AWS (AWSPlatform) platform and removed do-not-merge/needs-area labels Jun 10, 2026
@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.44509% with 84 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.61%. Comparing base (2f6b004) to head (cf44fbe).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...rshift-operator/controllers/nodepool/conditions.go 0.00% 24 Missing ⚠️
...ypershift-operator/controllers/nodepool/version.go 51.16% 18 Missing and 3 partials ⚠️
...erator/controllers/nodepool/nodepool_controller.go 56.52% 6 Missing and 4 partials ⚠️
hypershift-operator/controllers/nodepool/token.go 52.63% 6 Missing and 3 partials ⚠️
support/releaseinfo/deserialize.go 66.66% 6 Missing and 3 partials ⚠️
support/releaseinfo/releaseinfo.go 0.00% 6 Missing ⚠️
support/releaseinfo/registryclient_provider.go 0.00% 4 Missing ⚠️
hypershift-operator/controllers/nodepool/aws.go 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8714      +/-   ##
==========================================
+ Coverage   41.59%   41.61%   +0.02%     
==========================================
  Files         758      759       +1     
  Lines       93925    94073     +148     
==========================================
+ Hits        39066    39147      +81     
- Misses      52113    52168      +55     
- Partials     2746     2758      +12     
Files with missing lines Coverage Δ
hypershift-operator/controllers/nodepool/config.go 85.77% <100.00%> (+0.25%) ⬆️
...pershift-operator/controllers/nodepool/osstream.go 100.00% <100.00%> (ø)
ignition-server/cmd/run_local_ignitionprovider.go 0.00% <ø> (ø)
...ition-server/controllers/local_ignitionprovider.go 38.69% <ø> (+0.54%) ⬆️
...ition-server/controllers/tokensecret_controller.go 58.84% <ø> (-0.17%) ⬇️
hypershift-operator/controllers/nodepool/aws.go 70.02% <50.00%> (ø)
support/releaseinfo/registryclient_provider.go 0.00% <0.00%> (ø)
support/releaseinfo/releaseinfo.go 44.38% <0.00%> (-1.48%) ⬇️
hypershift-operator/controllers/nodepool/token.go 80.83% <52.63%> (-1.71%) ⬇️
support/releaseinfo/deserialize.go 55.31% <66.66%> (+15.31%) ⬆️
... and 3 more
Flag Coverage Δ
cmd-support 34.98% <48.64%> (+0.01%) ⬆️
cpo-hostedcontrolplane 43.59% <ø> (ø)
cpo-other 43.45% <ø> (ø)
hypershift-operator 51.65% <52.20%> (-0.01%) ⬇️

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
hypershift-operator/controllers/nodepool/version_test.go (1)

184-304: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add test coverage for OSImageStream status field in setNodesInfoStatus tests.

The new OSImageStream status field (set at version.go:103-108) is not verified by any of the three existing test cases. The test at lines 215-251 creates a Machine with NodeInfo, but expectedNodesInfo at line 246 only checks NodeVersions. Similarly, the other test cases don't verify the OSImageStream field.

Add OSImageStream expectations to the test cases to ensure the integration path correctly populates the status field from Machine OSImage data.

🤖 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 `@hypershift-operator/controllers/nodepool/version_test.go` around lines 184 -
304, The tests for setNodesInfoStatus don't assert the new OSImageStream status
field: update each test case (the ones constructing machines and
expectedNodesInfo in TestSetNodesInfoStatus) to include
Machine.Status.NodeInfo.OSImage (add OSImage value on the Machine with NodeInfo
where appropriate) and add the corresponding expected OSImageStream entries to
the expectedNodesInfo for each case (for the "machines exist" case set the
expected OSImageStream entry matching the Machine's OSImage; for the cases that
should clear status set expectedNodesInfo.OSImageStreams to empty). Ensure you
update the expectedNodesInfo structure used in the g.Expect comparison so
setNodesInfoStatus (the function under test) is validated for OSImageStream
population.
🧹 Nitpick comments (2)
hypershift-operator/controllers/nodepool/nodepool_controller.go (2)

764-786: ⚡ Quick win

Include requested stream in error message.

When streamMeta is nil (line 773), the error message mentions the architecture but not the requested stream. For consistency with the recommendation on defaultNodePoolAMI and to aid debugging, include the stream name when it's non-empty.

📝 Proposed fix
 	streamMeta := releaseImage.StreamMetadataForStream(s)
 	if streamMeta == nil {
-		return "", fmt.Errorf("release image stream metadata is nil, cannot determine GCP image for architecture %q", specifiedArch)
+		if s != "" {
+			return "", fmt.Errorf("release image has no metadata for stream %q, cannot determine GCP image for architecture %q", s, specifiedArch)
+		}
+		return "", fmt.Errorf("release image stream metadata is nil, cannot determine GCP image for architecture %q", specifiedArch)
 	}
🤖 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 `@hypershift-operator/controllers/nodepool/nodepool_controller.go` around lines
764 - 786, The error returned when streamMeta is nil in defaultNodePoolGCPImage
doesn't include the requested stream; update the error to include the stream
(variable s) when non-empty so callers can see which stream was requested (e.g.,
change the fmt.Errorf call that currently reports only the architecture to also
include the stream name), mirroring the behaviour in defaultNodePoolAMI and
keeping the existing architecture message.

739-761: ⚡ Quick win

Include requested stream in error message.

When streamMeta is nil (line 745), the error message doesn't mention which stream was requested. When debugging missing stream metadata (e.g., a payload lacking rhel-10 images), knowing the requested stream would speed diagnosis.

📝 Proposed fix
 	streamMeta := releaseImage.StreamMetadataForStream(s)
 	if streamMeta == nil {
-		return "", fmt.Errorf("release image stream metadata is nil")
+		if s != "" {
+			return "", fmt.Errorf("release image has no metadata for stream %q", s)
+		}
+		return "", fmt.Errorf("release image stream metadata is nil")
 	}
🤖 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 `@hypershift-operator/controllers/nodepool/nodepool_controller.go` around lines
739 - 761, The error returned in defaultNodePoolAMI when streamMeta is nil
doesn't include which stream was requested; update the error to include the
requested stream value (the local variable s / the first variadic stream
argument) so the message reads something like "release image stream metadata is
nil for stream %q" — modify the nil-check that currently returns
fmt.Errorf("release image stream metadata is nil") to include s for better
diagnostics.
🤖 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 `@hypershift-operator/controllers/nodepool/version.go`:
- Around line 103-108: The OSImageStream status is only updated when
osImageStreamFromMachines returns a non-empty string, leaving stale data
otherwise; modify the block around osImageStreamFromMachines(machines) so that
nodePool.Status.OSImageStream is always assigned: if stream != "" set
nodePool.Status.OSImageStream = hyperv1.OSImageStreamReference{Name: stream}
else clear it by assigning an empty/zero value (matching the pattern used for
NodesInfo at lines 99-101) so stale values are removed when no stream is
detected.

---

Outside diff comments:
In `@hypershift-operator/controllers/nodepool/version_test.go`:
- Around line 184-304: The tests for setNodesInfoStatus don't assert the new
OSImageStream status field: update each test case (the ones constructing
machines and expectedNodesInfo in TestSetNodesInfoStatus) to include
Machine.Status.NodeInfo.OSImage (add OSImage value on the Machine with NodeInfo
where appropriate) and add the corresponding expected OSImageStream entries to
the expectedNodesInfo for each case (for the "machines exist" case set the
expected OSImageStream entry matching the Machine's OSImage; for the cases that
should clear status set expectedNodesInfo.OSImageStreams to empty). Ensure you
update the expectedNodesInfo structure used in the g.Expect comparison so
setNodesInfoStatus (the function under test) is validated for OSImageStream
population.

---

Nitpick comments:
In `@hypershift-operator/controllers/nodepool/nodepool_controller.go`:
- Around line 764-786: The error returned when streamMeta is nil in
defaultNodePoolGCPImage doesn't include the requested stream; update the error
to include the stream (variable s) when non-empty so callers can see which
stream was requested (e.g., change the fmt.Errorf call that currently reports
only the architecture to also include the stream name), mirroring the behaviour
in defaultNodePoolAMI and keeping the existing architecture message.
- Around line 739-761: The error returned in defaultNodePoolAMI when streamMeta
is nil doesn't include which stream was requested; update the error to include
the requested stream value (the local variable s / the first variadic stream
argument) so the message reads something like "release image stream metadata is
nil for stream %q" — modify the nil-check that currently returns
fmt.Errorf("release image stream metadata is nil") to include s for better
diagnostics.
🪄 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: 56af0f53-9b8b-4ec2-8d7a-2a7a7c424a1a

📥 Commits

Reviewing files that changed from the base of the PR and between 4755e9c and 6c3e7d1.

📒 Files selected for processing (20)
  • hypershift-operator/controllers/nodepool/aws.go
  • hypershift-operator/controllers/nodepool/conditions.go
  • hypershift-operator/controllers/nodepool/config.go
  • hypershift-operator/controllers/nodepool/nodepool_controller.go
  • hypershift-operator/controllers/nodepool/rhel_stream.go
  • hypershift-operator/controllers/nodepool/rhel_stream_test.go
  • hypershift-operator/controllers/nodepool/token.go
  • hypershift-operator/controllers/nodepool/version.go
  • hypershift-operator/controllers/nodepool/version_test.go
  • ignition-server/cmd/run_local_ignitionprovider.go
  • ignition-server/controllers/local_ignitionprovider.go
  • ignition-server/controllers/local_ignitionprovider_test.go
  • ignition-server/controllers/tokensecret_controller.go
  • ignition-server/controllers/tokensecret_controller_test.go
  • support/releaseinfo/deserialize.go
  • support/releaseinfo/deserialize_test.go
  • support/releaseinfo/registry_mirror_provider.go
  • support/releaseinfo/registryclient_provider.go
  • support/releaseinfo/releaseinfo.go
  • support/releaseinfo/releaseinfo_test.go

Comment thread hypershift-operator/controllers/nodepool/version.go Outdated
@enxebre

enxebre commented Jun 10, 2026

Copy link
Copy Markdown
Member

/hold

@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 10, 2026
@enxebre enxebre changed the title CNTRLPLANE-3612: Add dual-stream RHEL 9/10 NodePool support DNM: CNTRLPLANE-3612: Add dual-stream RHEL 9/10 NodePool support Jun 10, 2026
Comment thread support/releaseinfo/releaseinfo.go Outdated
// discover constituent component image information.
type ReleaseImage struct {
*imageapi.ImageStream `json:",inline"`
StreamMetadata *CoreOSStreamMetadata `json:"streamMetadata"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we instead use the upstream types for github.com/coreos/stream-metadata-go

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good suggestion. I checked the upstream github.com/coreos/stream-metadata-go/stream types and they cover most of what we need: StreamCoreOSStreamMetadata, plus AwsImage, GcpImage, ContainerImage (KubeVirt), ReplicatedObject (PowerVS/IBMCloud) etc.

However, our custom types include Azure-specific extensions (CoreRHCOSImage, CoreAzureDisk, CoreMarketplace, CoreAzureMarketplaceImage) that don't exist in the upstream package — Azure marketplace data is accessed via rhcos.Extensions which has a different structure than what we parse today. Adopting upstream types would require:

  1. Adding the module as a dependency
  2. Updating all ~15 consumers to use upstream type names and field accessors
  3. Reworking the Azure marketplace path through rhcos.Extensions
  4. Verifying JSON tag compatibility (some differ: sha256 vs SHA256, etc.)

I'd prefer to do this as a follow-up PR to keep this one focused on the dual-stream plumbing. I'll create a JIRA for it. Sound reasonable?


AI-assisted response via Claude Code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point. The upstream github.com/coreos/stream-metadata-go package provides types like stream.Stream and stream.Arch that map to our custom CoreOSStreamMetadata and CoreOSArchitecture.

However, adopting the upstream types requires careful evaluation:

  1. The upstream types may include fields we don't need, increasing API surface
  2. Our types have custom JSON tags and HyperShift-specific nested structs (e.g., CoreOSGCPImage, CoreAzureMarketplace) that don't exist upstream
  3. The upstream types would introduce a new dependency that needs to be tracked for compatibility

I'd propose handling this as a follow-up PR to keep this one focused on the dual-stream logic. We can evaluate which upstream types map cleanly and adopt them incrementally. Would that work for you?


AI-assisted response via Claude Code

Comment thread support/releaseinfo/releaseinfo_test.go Outdated
// TestReleaseInfoKubeVirt tests validates the presence of the kubevirt images
func TestReleaseInfoKubeVirt(t *testing.T) {
metadata, err := DeserializeImageMetadata(fixtures.CoreOSBootImagesYAML_4_10)
metadata, _, err := DeserializeImageMetadata(fixtures.CoreOSBootImagesYAML_4_10)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we please have unit tests with a stub for dualstream

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

We already have dual-stream unit tests using a 5.0-dual-stream-bootimages.yaml fixture (embedded via fixtures.CoreOSDualStreamBootImagesYAML_5_0):

  • TestDeserializeDualStreamImageMetadata — verifies legacy single-stream parsing, multi-stream parsing (rhel-9/rhel-10), correct stream names, and per-stream AWS AMI differentiation
  • TestStreamMetadataForStream — verifies stream lookup for empty, known (rhel-10), and unknown stream names with correct fallback behavior

These are in support/releaseinfo/releaseinfo_test.go (lines 165–262). Would you like additional coverage for specific scenarios beyond what these already test?


AI-assisted response via Claude Code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The dual-stream deserialization is already covered by tests in this file:

  • TestDeserializeDualStreamImageMetadata (line 165): parses the dual-stream fixture, validates both rhel-9 and rhel-10 streams exist, verifies stream names, and checks per-stream AWS AMIs are different
  • TestStreamMetadataForStream (line 229): validates StreamMetadataForStream() returns the correct metadata for empty, known, and unknown stream names

For the TestReleaseInfoKubeVirt test at this line, the change is minimal — it only updates the call to DeserializeImageMetadata to accept the new 3-value return (adding _ for streamsMeta). The test still validates KubeVirt images from the 4.10 single-stream fixture.

Would you like additional tests specifically for KubeVirt/PowerVS with dual-stream data? The current dual-stream fixture (CoreOSDualStreamBootImagesYAML_5_0) does include per-stream data for AWS/GCP, but we could extend it to include KubeVirt and PowerVS entries as well.


AI-assisted response via Claude Code

}
// Default behavior for Linux/RHCOS AMIs
ami, err := defaultNodePoolAMI(region, arch, releaseImage)
ami, err := defaultNodePoolAMI(region, arch, releaseImage, nodePool.Spec.OSImageStream.Name)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there other platforms like azure that need to update how they pick default?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, good catch. Currently only AWS's defaultNodePoolAMI and defaultNodePoolGCPImage accept the stream parameter and use StreamMetadataForStream(). The following platforms still access releaseImage.StreamMetadata directly and would need updating:

  • Azure (azure.go:124) — getAzureMarketplaceMetadata uses releaseImage.StreamMetadata
  • GCP (gcp.go:171) — resolveGCPImage calls defaultNodePoolGCPImage without passing the stream name
  • KubeVirt (kubevirt/kubevirt.go:47) — uses releaseImage.StreamMetadata directly
  • PowerVS (powervs.go:112) — uses releaseImage.StreamMetadata directly
  • OpenStack (openstack/openstack.go:135,161) — uses releaseImage.StreamMetadata directly

For pre-5.0 releases this is fine (single stream = legacy StreamMetadata). For 5.0+ where StreamMetadata falls back to the first stream in the map, these platforms will still get a valid image — but they won't respect spec.osImageStream.name. I'll update them to use StreamMetadataForStream() consistently.


AI-assisted response via Claude Code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, good catch. Currently the following platforms need updating for dual-stream support:

  • GCP (gcp.go:171): defaultNodePoolGCPImage already accepts a stream ...string variadic, but resolveGCPImage doesn't pass nodePool.Spec.OSImageStream.Name yet
  • Azure (azure.go:39): defaultAzureNodePoolImage accesses releaseImage.StreamMetadata directly without going through StreamMetadataForStream()
  • PowerVS (powervs.go:112): getPowerVSImage accesses releaseImage.StreamMetadata.Architectures["ppc64le"] directly — though ppc64le may not have RHEL 10 images in the near term
  • KubeVirt (kubevirt.go:47): defaultImage accesses releaseImage.StreamMetadata directly

For this initial PR, I focused on AWS as the primary platform for RHEL 10 dual-stream. GCP and Azure should be updated to use StreamMetadataForStream() — I can add that in a follow-up or extend this PR if you'd prefer. PowerVS and KubeVirt are lower priority since ppc64le/s390x may not have RHEL 10 images initially.


AI-assisted response via Claude Code

return &ctrl.Result{}, fmt.Errorf("invalid RHEL stream configuration: %w", streamErr)
}
// Informational message when stream falls back to rhel-9 due to runc.
if nodePool.Spec.OSImageStream.Name == "" && stream == rhelStreamRHEL9 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

how do we know it was due to runc here? couldn't it be the payload is < 5?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good question. When specStream == "", getRHELStream() returns exactly these values:

Release usesRunc Returns
< 5.0 false "" (empty — legacy)
< 5.0 true "" (empty — legacy)
>= 5.0 false "rhel-10" (default)
>= 5.0 true "rhel-9" (runc fallback)

So the condition specStream == "" && stream == rhelStreamRHEL9 can only be true when rv >= 5.0 && usesRunc — pre-5.0 payloads return empty string, not "rhel-9". The runc attribution in the message is correct.

That said, the code doesn't make this obvious at the call site. Would it be clearer to also check configGenerator.usesRunc explicitly in the condition?


AI-assisted response via Claude Code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good question. Looking at getRHELStream() in rhel_stream.go, when specStream == "" (no explicit stream set), the possible return values are:

  1. rv >= 5.0 && usesRunc → returns "rhel-9" (this is the only path that returns "rhel-9" with empty specStream)
  2. rv >= 5.0 && !usesRunc → returns "rhel-10"
  3. rv < 5.0 → returns "" (legacy single-stream)

So the condition nodePool.Spec.OSImageStream.Name == "" && stream == rhelStreamRHEL9 can only be true via case 1 (runc fallback). Pre-5.0 payloads return "", not "rhel-9".

That said, the message could be clearer. I could add a comment explaining this reasoning, or make the condition more explicit by also checking configGenerator.usesRunc directly. Would that help?


AI-assisted response via Claude Code

cg.rolloutConfig.additionalTrustBundleName = hostedCluster.Spec.AdditionalTrustBundle.Name
}

// rhelStream is populated directly from spec.osImageStream.name (empty when unset).

@enxebre enxebre Jun 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we error here if userunC and rhelStream=10?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is already handled downstream. In conditions.go:394, after the config is generated, we call:

```go
stream, streamErr := getRHELStream(nodePool.Spec.OSImageStream.Name, releaseVersion, configGenerator.usesRunc)
```

getRHELStream returns an error for rhelStream=rhel-10 + usesRunc:
```go
case specStream == rhelStreamRHEL10 && usesRunc:
return "", fmt.Errorf("OS stream %s is incompatible with default_runtime=runc; RHEL 10 does not ship runc", rhelStreamRHEL10)
```

The config generator itself populates `rhelStream` from the raw spec value at line 104 — it's just storing the user's input. The validation happens in validMachineConfigCondition which runs getRHELStream() to catch the incompatible combination and sets a clear condition message. So the validation is there, just in a different layer (conditions vs config).


AI-assisted response via Claude Code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is already handled by getRHELStream() in rhel_stream.go:36-37:

case specStream == rhelStreamRHEL10 && usesRunc:
    return "", fmt.Errorf("OS stream %s is incompatible with default_runtime=runc; RHEL 10 does not ship runc", rhelStreamRHEL10)

The validation happens in validMachineConfigCondition() (conditions.go:394) which calls getRHELStream() and sets NodePoolValidMachineConfigCondition to False when this error is returned. So the error is surfaced as a condition before we ever reach NewConfigGenerator.

The code at this line (config.go:101) runs during NewConfigGenerator, which is called before the getRHELStream() validation in conditions.go. However, cg.rhelStream here is only used for the hash — the actual incompatibility validation is handled separately. The usesRunc flag is set later during generateMCORawConfig (line 106) when parsing ContainerRuntimeConfig manifests.


AI-assisted response via Claude Code

// This is kept like this for now to contain the scope of the refactor and avoid backward compatibility issues.
func (cg *ConfigGenerator) Hash() string {
return supportutil.HashSimple(cg.mcoRawConfig + cg.releaseImage.Version() + cg.pullSecretName + cg.additionalTrustBundleName + cg.globalConfig)
return supportutil.HashSimple(cg.mcoRawConfig + cg.releaseImage.Version() + cg.pullSecretName + cg.additionalTrustBundleName + cg.globalConfig + cg.rhelStream)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we have coverage to validate this doesn't change the current hash when rhelStream is empty? otherwise that would trigger a rollout

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

When `rhelStream` is empty (the default), the hash input is:

```
mcoRawConfig + version + pullSecretName + additionalTrustBundleName + globalConfig + ""
```

Since `"abc" + ""` == `"abc"` in Go string concatenation, appending an empty string produces the same hash as before. No rollout is triggered.

That said, an explicit test proving this invariant would be valuable — I'll add a test case that computes the hash with `rhelStream=""` and verifies it matches the hash computed without the `rhelStream` field present at all.


AI-assisted response via Claude Code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes — there's explicit test coverage for this in config_test.go:

TestHash (line 450):

{
    name:     "When rhelStream is empty it should not change the hash",
    ...
    rhelStream: "",
    expected:   baseCaseHash,  // identical to base case without rhelStream field
},

This test verifies that an empty rhelStream (the default when spec.osImageStream is unset) produces the exact same hash as the base case. Since rhelStream defaults to "" and string concatenation with "" is a no-op, the hash input is identical to pre-PR behavior.


AI-assisted response via Claude Code

// This is kept like this for now to contain the scope of the refactor and avoid backward compatibility issues.
func (cg *ConfigGenerator) HashWithoutVersion() string {
return supportutil.HashSimple(cg.mcoRawConfig + cg.pullSecretName + cg.additionalTrustBundleName)
return supportutil.HashSimple(cg.mcoRawConfig + cg.pullSecretName + cg.additionalTrustBundleName + cg.rhelStream)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we have coverage to validate this doesn't change the current hash when rhelStream is empty? otherwise that would trigger a rollout

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same answer as above — when rhelStream is empty (the default/unset case), appending "" to the hash input string is a no-op: "abc" + "" == "abc". The hash is identical to the old code. I'll make the test cover both Hash() and HashWithoutVersion() to validate this invariant explicitly.


AI-assisted response via Claude Code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same as above — TestHashWithoutVersion (line 576) has the same coverage:

{
    name:     "When rhelStream is empty it should not change the hash",
    ...
    rhelStream: "",
    expected:   baseCaseHash,  // "85234650" — identical to base case
},

Both Hash() and HashWithoutVersion() are covered. Empty rhelStream → no hash change → no rollout.


AI-assisted response via Claude Code

}
case *mcfgv1.ContainerRuntimeConfig:
// Detect runc usage for dual-stream RHEL compatibility
if obj.Spec.ContainerRuntimeConfig != nil &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this how the MCO validates this? please let this be a unit tested func

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done. The runc detection logic has been extracted into containerRuntimeUsesRunc() in rhel_stream.go and unit tested in rhel_stream_test.go (TestContainerRuntimeUsesRunc) with coverage for runc, crun, nil config, and empty runtime cases. The MCO uses ContainerRuntimeConfig.Spec.ContainerRuntimeConfig.DefaultRuntime to determine the runtime — our check mirrors that.


AI-assisted response via Claude Code

@enxebre

enxebre commented Jun 11, 2026

Copy link
Copy Markdown
Member

Code Review: PR #8714 — Dual-stream RHEL 9/10 NodePool Support

Reviewer: AI-assisted review (Claude Code with HyperShift SME agents)
Language: Go | Profile: HyperShift


Overall Verdict: FAIL (3 blocking issues)

The end-to-end architecture (spec → controller → token → ignition → MCC) is sound, and the hash stability design (explicit stream only in hash to avoid fleet-wide rollouts on 5.0 upgrade) is well-considered. Pure functions (getRHELStream, detectRHELStreamFromOSImage, osImageStreamFromMachines) are well-tested. However, there are a build failure, a data loss bug in disconnected environments, and a nil-safety issue that block merge.


Required Actions (Blocking)

1. Build failure — run_local_ignitionprovider.go:114

ignition-server/cmd/run_local_ignitionprovider.go:114:70: not enough arguments in call to p.GetPayload

The GetPayload interface gained a 7th osStream parameter, but RunLocalIgnitionProviderOptions.Run still passes only 6 arguments. Needs "" as the last argument.

2. RegistryMirrorProviderDecorator.Lookup drops StreamsMetadataregistry_mirror_provider.go:42

The Lookup method constructs a new ReleaseImage but does not propagate StreamsMetadata:

return &ReleaseImage{
    ImageStream:    imageStream,
    StreamMetadata: releaseImage.StreamMetadata,
    // StreamsMetadata: missing!
}

Any cluster using registry mirror overrides (common in disconnected/air-gapped environments) will silently lose multi-stream metadata. resolveStreamMetadata() will return an error when the user sets spec.osImageStream.name in these environments. Fix: add StreamsMetadata: releaseImage.StreamsMetadata.

3. PowerVS nil-safety — powervs.go:112

getPowerVSImage() accesses releaseImage.StreamMetadata.Architectures["ppc64le"] directly without going through resolveStreamMetadata(). If a future payload uses only the streams key (no legacy stream key), StreamMetadata will be nil, causing a panic. At minimum, add a nil check. Ideally, use the shared resolveStreamMetadata() helper.


Recommended Improvements (Non-blocking)

Architecture & Correctness

4. Inconsistent stream resolution across platforms — AWS/GCP use resolveStreamMetadata() which returns an error for unknown streams. Azure (azure.go:123-129) and KubeVirt (kubevirt/kubevirt.go:49-53) implement inline fallback that silently ignores unknown streams. All should use the shared helper for consistent error behavior.

5. Karpenter AMI path misses streamtoken.go:431 calls defaultNodePoolAMI(region, arch, releaseImage) without passing the stream name. Karpenter-managed NodePools will always get the legacy/default stream AMI, ignoring OSImageStream.

6. Non-deterministic map iteration in DeserializeImageMetadata fallback — When the stream key is absent but streams is present, the code iterates the streamsMeta map and returns the first entry. Map iteration in Go is non-deterministic. Should pick a deterministic default (e.g., sorted first key).

7. OpenStack nil-safetyOpenstackDefaultImage() and OpenStackReleaseImage() (openstack.go:135,158) access releaseImage.StreamMetadata.Architectures["x86_64"] directly. Same nil-safety risk as PowerVS.

Code Quality

8. Variadic stream ...string is unidiomatic (nodepool_controller.go) — defaultNodePoolAMI and defaultNodePoolGCPImage use variadic to make the parameter optional, but all callers pass 0 or 1 arguments. Use a plain string parameter where "" means "use default" — the resolveStreamMetadata helper already uses this pattern correctly.

9. detectRHELStreamFromOSImage uses raw string literals (version.go) — Returns "rhel-10" and "rhel-9" instead of the constants RHELStream10/RHELStream9 defined in the same package.

10. GetPayload has 7 positional string parameters — All string typed with no compile-time distinction. Consider refactoring to a PayloadRequest struct. At minimum, add a // TODO noting this debt.

11. Raw YAML for OSImageStream CR (local_ignitionprovider.go) — Uses fmt.Sprintf with %q to template YAML and hardcodes machineconfiguration.openshift.io/v1alpha1. No compile-time contract with the MCO type. The HyperShift API comment references v1, creating a version mismatch. Consider using a typed Go struct with proper serialization.

Test Gaps

12. Missing tests for key integration points:

Function File Gap
resolveStreamMetadata() nodepool_controller.go Zero tests — core dispatch logic for multi-stream
DeserializeMultiStreamImageMetadata() deserialize.go Zero tests — parses new multi-stream ConfigMap format
usesRunc detection config.go No test with defaultRuntime=runc verifying cg.usesRunc=true
defaultNodePoolAMI with stream nodepool_controller.go Variadic stream param never exercised
rhelStream in Hash config.go No test verifying hash changes when rhelStream changes
os-stream in token secret token.go No test verifying the key is written

Documentation

13. No forced migration path for existing RHEL 9 nodes — The implicit default switch from RHEL 9 to RHEL 10 on 5.0+ happens at the ignition payload level without changing the rollout hash. Existing nodes stay on RHEL 9; only new nodes get RHEL 10. This is likely intentional but should be documented explicitly in the enhancement.


Platform Coverage Summary

Platform Stream-aware? Status
AWS Correct via resolveStreamMetadata()
GCP Correct via resolveStreamMetadata()
Azure ⚠️ Inline resolution — should use shared helper
KubeVirt ⚠️ Inline resolution — should use shared helper
PowerVS Nil-safety risk (blocking)
OpenStack Nil-safety risk
Karpenter Stream argument not passed

What's Good

  • Hash stability design — Using the raw spec value (not resolved default) for the rollout hash avoids fleet-wide rollouts when upgrading to 5.0. Well-commented.
  • getRHELStream() is clean — Pure function with clear switch-case logic and comprehensive tests (10 cases).
  • Token secret flow — The os-stream key absence for pre-5.0 is handled correctly (nil → empty string → no manifest).
  • OSImageStream CR timing — Written after MCO but before MCC, which is the correct placement in the ignition pipeline.
  • usesRunc detection — Correctly re-initialized per reconcile via fresh ConfigGenerator construction.

Generated with Claude Code using HyperShift SME agents (control-plane, data-plane, cloud-provider, API, architect)


// rhelStream is populated directly from spec.osImageStream.name (empty when unset).
// Using the resolved value from getRHELStream() would inject implicit defaults into
// the hash, triggering fleet-wide rollouts on upgrade.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should only let this change the hash and trigger a rollout if the value is different from the one in status

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Interesting suggestion. Let me walk through the current behavior and the tradeoff:

Current design: rhelStream in the hash comes directly from spec.osImageStream.name (empty when unset). This means:

  • Empty → empty (upgrade to 5.0, no explicit stream set): hash unchanged, no rollout ✅
  • Empty → "rhel-10" (user explicitly sets stream): hash changes, rollout triggered ✅
  • "rhel-9" → "rhel-10" (user changes stream): hash changes, rollout triggered ✅

Your suggestion (compare with status): If spec == status, skip from hash. This would prevent a rollout when the user explicitly sets the stream to what's already running (e.g., setting "rhel-10" on a node already running rhel-10).

The tradeoff: this would require passing nodePool.Status.OSImageStream.Name into the ConfigGenerator, adding a coupling between the config hash computation and status. Currently the config generator is deliberately a pure function of spec inputs.

Also, status.osImageStream is populated from CAPI Machine NodeInfo (detected from running nodes), not from the controller's intent. There could be a window where status lags — e.g., the user sets rhel-10 but nodes haven't been updated yet, so status still shows rhel-9.

For now, the only case that would cause a "spurious" rollout is when a user explicitly sets the stream to match what they're already running — which seems rare and arguably intentional (explicit declarative intent). Would you still like this changed, or is the current behavior acceptable?


AI-assisted response via Claude Code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point. The current design uses spec.osImageStream.name directly in the hash, which means:

  • Empty → empty string → hash unchanged → no rollout ✅
  • User sets "rhel-10" → hash changes → rollout ✅ (intended)
  • User sets "rhel-9" on a cluster already running rhel-9 → hash changes → rollout ⚠️ (unnecessary)

Your suggestion to compare spec vs status would avoid the unnecessary rollout in case 3. Implementation would look something like:

// Only include rhelStream in hash if it differs from the observed stream in status,
// preventing unnecessary rollouts when explicitly setting the current stream.
if nodePool.Spec.OSImageStream.Name != nodePool.Status.OSImageStream.Name {
    cg.rolloutConfig.rhelStream = nodePool.Spec.OSImageStream.Name
}

However, there's a subtle concern: this creates a dependency between the hash calculation and the status, which is normally populated asynchronously from observed machine state. If status hasn't been populated yet (e.g., new NodePool), we'd need to handle that edge case.

Would you like me to implement this approach? The tradeoff is: simpler code (current) vs. avoiding unnecessary rollouts when the user sets spec.osImageStream to match the already-running stream.


AI-assisted response via Claude Code

@enxebre

enxebre commented Jun 11, 2026

Copy link
Copy Markdown
Member

/address-review-comments

@github-actions

Copy link
Copy Markdown

🤖 Addressing review comments: workflow run

@openshift-ci openshift-ci Bot added the area/platform/kubevirt PR/issue for KubeVirt (KubevirtPlatform) platform label Jun 11, 2026
@openshift-ci openshift-ci Bot added area/platform/openstack PR/issue for OpenStack (OpenStackPlatform) platform area/platform/powervs PR/issue for PowerVS (PowerVSPlatform) platform labels Jun 11, 2026
@enxebre

enxebre commented Jun 11, 2026

Copy link
Copy Markdown
Member

/address-review-comments

@github-actions

Copy link
Copy Markdown

🤖 Addressing review comments: workflow run

@enxebre

enxebre commented Jun 11, 2026

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Author

Re: #8714 (comment)

Review Comments Addressed

Addressed the code review from #8714 (comment).

Blocking Issues (all 3 fixed)

All three blocking issues were already resolved in commit 18681288b:

# Issue Status
1 Build failure — GetPayload missing 7th arg ✅ Fixed (already had 7 args)
2 RegistryMirrorProviderDecorator.Lookup drops StreamsMetadata ✅ Fixed (StreamsMetadata already propagated)
3 PowerVS nil-safety ✅ Fixed (nil check added)

Non-blocking Items Addressed

Fixed in new commit f321415c3:

# Issue Fix
5 Karpenter AMI misses stream Passed rhelStream through setKarpenterAMILabelsdefaultNodePoolAMI so Karpenter-managed NodePools get stream-specific AMIs
6 Non-deterministic map iteration in DeserializeImageMetadata Sort map keys before picking fallback entry when stream key is absent

Already fixed in commit 18681288b:

# Issue Fix
7 OpenStack nil-safety Nil checks added to OpenstackDefaultImage and OpenStackReleaseImage
9 Raw string literals instead of constants Replaced with rhelStreamRHEL10/rhelStreamRHEL9 constants
12 Missing tests (partial) Added tests for containerRuntimeUsesRunc, rhelStream in hash, DeserializeMultiStreamImageMetadata, StreamMetadataForStream

Items Deferred as Follow-up

# Issue Rationale
4 Azure/KubeVirt should use shared helper These platforms don't support OSImageStream yet — using the shared helper would be premature. Nil-safety checks were added as the immediate fix.
8 Variadic stream ...string is unidiomatic Valid but a larger refactor touching all callers; better as a follow-up to keep this PR focused
10 GetPayload has 7 positional string parameters Acknowledged technical debt — will track as follow-up for PayloadRequest struct refactor
11 Raw YAML for OSImageStream CR Valid concern about compile-time safety; larger refactor better suited for follow-up
13 Document implicit RHEL 9→10 migration behavior Enhancement-level documentation — will address in the enhancement doc

Build & Test Status

  • go build succeeds for all changed packages
  • All support/releaseinfo tests pass (including new dual-stream tests)
  • Note: nodepool package tests have a pre-existing build failure in secret_janitor_test.go (undefined releaseinfo.NewMockProviderWithRegistryOverrides) — this exists on the branch before any of my changes

AI-assisted response via Claude Code

@enxebre

enxebre commented Jun 11, 2026

Copy link
Copy Markdown
Member

rebase and create a tmp.txt file in the root folder with a literal "test".

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 11, 2026
@enxebre enxebre removed request for devguyio and muraee June 11, 2026 16:17
@enxebre enxebre force-pushed the fix-CNTRLPLANE-3612 branch from d912c4b to 6fee43e Compare June 11, 2026 17:34
@openshift-ci openshift-ci Bot added area/platform/azure PR/issue for Azure (AzurePlatform) platform area/platform/gcp PR/issue for GCP (GCPPlatform) platform and removed needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Jun 11, 2026

@coderabbitai coderabbitai Bot left a comment

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.

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 (4)
hypershift-operator/controllers/nodepool/aws.go (1)

434-434: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing nil-safety guard for releaseImage.StreamMetadata.

Line 434 dereferences releaseImage.StreamMetadata.Architectures without checking if StreamMetadata is nil. When a payload uses only the new streams metadata format (without legacy single-stream), StreamMetadata can be nil, causing a panic.

This is the same blocking nil-safety issue the reviewer flagged for PowerVS and OpenStack.

🛡️ Add nil guard before accessing StreamMetadata
 func getWindowsAMI(region string, specifiedArch string, releaseImage *releaseinfo.ReleaseImage) (string, error) {
 	if releaseImage == nil {
 		return "", fmt.Errorf("release image is nil")
 	}
 
 	if releaseImage.StreamMetadata == nil {
-		return "", fmt.Errorf("release image stream metadata is nil")
+		return "", fmt.Errorf("release image stream metadata is nil")
 	}
 
 	archData, foundArch := releaseImage.StreamMetadata.Architectures[hyperv1.ArchAliases[specifiedArch]]
🤖 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 `@hypershift-operator/controllers/nodepool/aws.go` at line 434, Guard against
nil releaseImage.StreamMetadata before accessing Architectures: in the code path
using releaseImage.StreamMetadata.Architectures (the line computing archData,
foundArch), add a nil check on releaseImage.StreamMetadata and handle the nil
case (e.g., skip this lookup, set foundArch=false or use the alternative streams
metadata lookup) to avoid dereferencing a nil StreamMetadata; update any
downstream logic that assumes foundArch accordingly. Ensure references include
releaseImage, StreamMetadata, Architectures, archData/foundArch,
hyperv1.ArchAliases and specifiedArch so the change is applied exactly where the
lookup happens.
hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go (1)

47-56: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Multiple nil-safety risks in KubeVirt image resolution.

Two nil-dereference risks:

  1. Line 47: releaseImage.StreamMetadata.Architectures — if StreamMetadata is nil (multi-stream-only payload), this panics
  2. Line 53: arch.Images.Kubevirt.DigestRef — if arch.Images.Kubevirt is nil, accessing .DigestRef panics

These are the same blocking nil-safety issues flagged for PowerVS/OpenStack.

🛡️ Add nil guards before dereferencing
 func defaultImage(nodePoolArch string, releaseImage *releaseinfo.ReleaseImage) (string, string, error) {
+	if releaseImage.StreamMetadata == nil {
+		return "", "", fmt.Errorf("release image stream metadata is nil")
+	}
+
 	var archName string
 	switch nodePoolArch {
 	case hyperv1.ArchitectureS390X:
 		archName = hyperv1.ArchitectureS390X
 	default:
 		archName = hyperv1.ArchAliases[hyperv1.ArchitectureAMD64]
 	}
 	arch, foundArch := releaseImage.StreamMetadata.Architectures[archName]
 
 	if !foundArch {
 		return "", "", fmt.Errorf("couldn't find OS metadata for architecture %q", archName)
 	}
 
+	if arch.Images.Kubevirt == nil {
+		return "", "", fmt.Errorf("no kubevirt image metadata present in release")
+	}
 	if arch.Images.Kubevirt.DigestRef == "" {
 		return "", "", fmt.Errorf("no kubevirt image metadata present in release")
 	}
🤖 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 `@hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go` around lines
47 - 56, Check for nil StreamMetadata and nil nested image structs before
dereferencing: guard that releaseImage.StreamMetadata is not nil before indexing
into StreamMetadata.Architectures[archName], and after finding arch, ensure
arch.Images and arch.Images.Kubevirt are non-nil before reading DigestRef; if
any are nil return a clear error (e.g., "no stream metadata", "no image metadata
for architecture %q", or "no kubevirt image metadata present in release") so
containerImage is only assigned when these checks pass.
hypershift-operator/controllers/nodepool/openstack/openstack.go (1)

134-135: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing nil-safety guard for releaseImage.StreamMetadata (blocking issue).

Line 135 dereferences releaseImage.StreamMetadata.Architectures without checking if StreamMetadata is nil. When a payload uses only the new streams metadata format, StreamMetadata can be nil, causing a panic.

This is the blocking nil-safety issue the reviewer explicitly flagged for OpenStack in the PR objectives summary.

🛡️ Add nil guard before accessing StreamMetadata
 func OpenstackDefaultImage(releaseImage *releaseinfo.ReleaseImage) (string, string, error) {
+	if releaseImage.StreamMetadata == nil {
+		return "", "", fmt.Errorf("release image stream metadata is nil")
+	}
+
 	arch, foundArch := releaseImage.StreamMetadata.Architectures["x86_64"]
 	if !foundArch {
🤖 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 `@hypershift-operator/controllers/nodepool/openstack/openstack.go` around lines
134 - 135, OpenstackDefaultImage currently dereferences
releaseImage.StreamMetadata without a nil check; add a guard at the start of
OpenstackDefaultImage to check if releaseImage == nil or
releaseImage.StreamMetadata == nil before accessing
StreamMetadata.Architectures, and if nil return a clear error (or fallback to
the alternative streams metadata if available) so the function never panics when
StreamMetadata is absent; reference the symbols OpenstackDefaultImage,
releaseImage, StreamMetadata, and Architectures when applying the check and
returning the error/fallback.
hypershift-operator/controllers/nodepool/azure.go (1)

124-139: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Multiple nil-safety risks in marketplace metadata extraction.

Lines 128 and 136-137 dereference nested structs without nil-guarding intermediate fields:

  1. Line 128: releaseImage.StreamMetadata.Architectures — if StreamMetadata is nil (multi-stream-only payload), this panics
  2. Line 136: archData.RHCOS.Marketplace.Azure.NoPurchasePlan — if RHCOS.Marketplace or Marketplace.Azure is nil, this panics

These are the same blocking nil-safety issues flagged for PowerVS/OpenStack.

🛡️ Add nil guards for nested struct access
 func getAzureMarketplaceMetadata(releaseImage *releaseinfo.ReleaseImage, arch string) (*azureMarketplaceMetadata, error) {
 	if releaseImage.StreamMetadata == nil {
-		return nil, nil // No stream metadata available
+		return nil, nil
 	}
 
 	archData, foundArch := releaseImage.StreamMetadata.Architectures[arch]
 	if !foundArch {
 		return nil, fmt.Errorf("architecture %s not found in stream metadata", arch)
 	}
 
+	// Check for nil at each level of nested access
+	if archData.RHCOS.Marketplace == nil || archData.RHCOS.Marketplace.Azure == nil {
+		return nil, nil // No Azure marketplace data available
+	}
+
 	azureMarketplace := archData.RHCOS.Marketplace.Azure.NoPurchasePlan
 	if azureMarketplace.HyperVGen1 == nil && azureMarketplace.HyperVGen2 == nil {
🤖 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 `@hypershift-operator/controllers/nodepool/azure.go` around lines 124 - 139,
The code dereferences nested fields without nil checks causing panics; add
explicit nil guards before accessing releaseImage.StreamMetadata and before
archData.RHCOS, archData.RHCOS.Marketplace, archData.RHCOS.Marketplace.Azure and
archData.RHCOS.Marketplace.Azure.NoPurchasePlan; specifically, in the function
that reads releaseImage.StreamMetadata.Architectures and assigns
azureMarketplace := archData.RHCOS.Marketplace.Azure.NoPurchasePlan, first check
if releaseImage.StreamMetadata == nil (return nil,nil), then after finding
archData verify archData.RHCOS != nil && archData.RHCOS.Marketplace != nil &&
archData.RHCOS.Marketplace.Azure != nil &&
archData.RHCOS.Marketplace.Azure.NoPurchasePlan != nil (return nil,nil if any
are nil) before using azureMarketplace and its HyperVGen1/Gen2 fields.
🧹 Nitpick comments (5)
hypershift-operator/controllers/nodepool/powervs.go (1)

111-131: ⚡ Quick win

PowerVS doesn't support stream-aware image selection.

getPowerVSImage accesses releaseImage.StreamMetadata directly without accepting a stream parameter. This means PowerVS images can't respect spec.osImageStream for dual-stream RHEL 9/10 support.

For consistency with AWS's stream-aware pattern and to support multi-stream payloads, consider adding an optional stream ...string parameter and using releaseImage.StreamMetadataForStream(stream).

🤖 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 `@hypershift-operator/controllers/nodepool/powervs.go` around lines 111 - 131,
The getPowerVSImage function currently reads releaseImage.StreamMetadata
directly; change its signature to accept an optional stream parameter (e.g.,
stream ...string) and obtain the architecture metadata via
releaseImage.StreamMetadataForStream(stream) instead of
releaseImage.StreamMetadata so PowerVS becomes stream-aware; update the function
body to use the returned StreamMetadata for the "ppc64le" lookup and keep the
rest of the logic unchanged, and then update all call sites of getPowerVSImage
to pass the stream when available.
hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go (1)

39-67: ⚡ Quick win

KubeVirt doesn't support stream-aware image selection.

defaultImage accesses releaseImage.StreamMetadata directly without accepting a stream parameter. This means KubeVirt images can't respect spec.osImageStream for dual-stream RHEL 9/10 support.

For consistency with AWS and to support multi-stream payloads, consider adding an optional stream ...string parameter and using releaseImage.StreamMetadataForStream(stream).

🤖 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 `@hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go` around lines
39 - 67, The defaultImage function currently reads releaseImage.StreamMetadata
directly, which prevents honoring spec.osImageStream; modify func
defaultImage(nodePoolArch string, releaseImage *releaseinfo.ReleaseImage, stream
...string) (string, string, error) to accept an optional stream parameter, call
releaseImage.StreamMetadataForStream(stream) to obtain the appropriate
StreamMetadata (falling back when stream is empty), then use the returned
metadata for the architecture lookup (arch, foundArch :=
streamMetadata.Architectures[archName]) and keep the existing validation and
return values unchanged; ensure callers of defaultImage are updated to pass the
optional stream where stream-aware selection is required.
hypershift-operator/controllers/nodepool/openstack/openstack.go (1)

132-153: ⚡ Quick win

OpenStack doesn't support stream-aware image selection.

OpenstackDefaultImage accesses releaseImage.StreamMetadata directly without accepting a stream parameter. This means OpenStack images can't respect spec.osImageStream for dual-stream RHEL 9/10 support, unlike AWS which correctly uses stream-aware selection.

For consistency with AWS and to support multi-stream payloads, consider adding an optional stream ...string parameter and using releaseImage.StreamMetadataForStream(stream).

🤖 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 `@hypershift-operator/controllers/nodepool/openstack/openstack.go` around lines
132 - 153, OpenstackDefaultImage currently reads releaseImage.StreamMetadata
directly and thus ignores spec.osImageStream; change the function signature to
accept an optional variadic stream parameter (e.g.,
OpenstackDefaultImage(releaseImage *releaseinfo.ReleaseImage, stream ...string))
and use releaseImage.StreamMetadataForStream(stream) to obtain the stream-aware
metadata before looking up Architectures["x86_64"], Artifacts["openstack"],
Formats["qcow2.gz"] and the disk; ensure the function otherwise returns the same
(disk.Location, disk.SHA256, error) and preserves existing error messages when
lookups fail.
hypershift-operator/controllers/nodepool/aws.go (1)

424-453: ⚡ Quick win

Windows AMI path doesn't support stream-aware image selection.

getWindowsAMI accesses releaseImage.StreamMetadata directly and doesn't accept a stream parameter, unlike the Linux/RHCOS path which correctly passes nodePool.Spec.OSImageStream.Name to defaultNodePoolAMI (lines 137, 364). This means Windows AMIs can't respect spec.osImageStream for dual-stream RHEL 9/10 support.

For consistency with the Linux path and to properly support multi-stream payloads, consider:

  1. Adding an optional stream ...string parameter to getWindowsAMI
  2. Using releaseImage.StreamMetadataForStream(stream) instead of direct StreamMetadata access
  3. Passing nodePool.Spec.OSImageStream.Name from callers (lines 343, 130)
🤖 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 `@hypershift-operator/controllers/nodepool/aws.go` around lines 424 - 453,
getWindowsAMI currently reads releaseImage.StreamMetadata directly so it ignores
spec.osImageStream and cannot select stream-aware Windows AMIs; change
getWindowsAMI to accept an optional stream parameter (e.g., stream ...string)
and use releaseImage.StreamMetadataForStream(stream) instead of
releaseImage.StreamMetadata, update callers (notably defaultNodePoolAMI and
places that resolve AMIs) to pass nodePool.Spec.OSImageStream.Name when present
so Windows AMI lookup mirrors the Linux/RHCOS stream-aware behavior.
hypershift-operator/controllers/nodepool/azure.go (1)

122-167: ⚡ Quick win

Azure marketplace doesn't support stream-aware image selection.

getAzureMarketplaceMetadata accesses releaseImage.StreamMetadata directly without accepting a stream parameter, unlike AWS's defaultNodePoolAMI which correctly uses StreamMetadataForStream(). This means Azure can't respect spec.osImageStream for dual-stream RHEL 9/10 support.

For consistency and to properly support multi-stream payloads, consider adding an optional stream ...string parameter and using releaseImage.StreamMetadataForStream(stream).

🤖 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 `@hypershift-operator/controllers/nodepool/azure.go` around lines 122 - 167,
getAzureMarketplaceMetadata currently reads releaseImage.StreamMetadata directly
and thus ignores spec.osImageStream; change its signature to accept an optional
stream parameter (e.g., getAzureMarketplaceMetadata(releaseImage
*releaseinfo.ReleaseImage, arch string, stream ...string)) and use
releaseImage.StreamMetadataForStream(stream) (falling back to existing
StreamMetadata when no stream passed) instead of direct access to
releaseImage.StreamMetadata; update internal references (archData lookup,
azureMarketplace extraction) to use the returned stream metadata so Azure
respects Stream-aware image selection consistent with defaultNodePoolAMI and
supports dual-stream RHEL 9/10 scenarios.
🤖 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 `@hypershift-operator/controllers/nodepool/aws_test.go`:
- Around line 716-733: The test case named "When RHELCoreOSExtensions is nil, it
should return error" has an expectedError string that doesn't match the actual
error returned by getWindowsAMI; update the test's expectedError to match the
production error path (e.g., the "no aws-winli regions data found..." message)
or change the fixture to trigger the rhel-coreos-extensions error if that's the
intended behavior; locate the test struct in aws_test.go (the case with name
matching the above), and set expectedError to the exact error returned by
getWindowsAMI for that input shape (or adjust the releaseImage/StreamMetadata so
getWindowsAMI hits the rhel-coreos-extensions branch).

In `@hypershift-operator/controllers/nodepool/powervs_test.go`:
- Around line 11-52: Add a test case to TestGetPowerVSImage that covers
releaseImage == nil and releaseImage.StreamMetadata == nil to prevent the
nil-dereference panic, and update getPowerVSImage to defensively check for nil
releaseImage and nil StreamMetadata at the top (returning a clear error like
"release image metadata is nil" or similar) before accessing
StreamMetadata.Architectures; reference getPowerVSImage in your patch and ensure
the new tests assert an error containing the new message.

In `@hypershift-operator/controllers/nodepool/powervs.go`:
- Around line 117-119: Add a nil-check for releaseImage.StreamMetadata before
dereferencing it: ensure releaseImage.StreamMetadata != nil before accessing
releaseImage.StreamMetadata.Architectures (the code that constructs arch and
later checks arch.Images.PowerVS.Regions). If StreamMetadata is nil, return an
appropriate error (similar to the existing error path) so the function does not
panic; reference releaseImage, StreamMetadata, Architectures, arch, Images,
PowerVS, and Regions when locating where to insert this guard.

In `@ignition-server/cmd/run_local_ignitionprovider.go`:
- Line 114: The call to GetPayload is ignoring the CLI --feature-gate-manifest
because the fifth argument is a literal empty string; update the GetPayload call
so it passes the stored option o.FeatureGateManifest (the same value previously
set on the provider) instead of the empty string. Locate the call to
p.GetPayload(...) and replace the 5th parameter with o.FeatureGateManifest so
the feature-gate manifest flag is forwarded to GetPayload.

---

Outside diff comments:
In `@hypershift-operator/controllers/nodepool/aws.go`:
- Line 434: Guard against nil releaseImage.StreamMetadata before accessing
Architectures: in the code path using releaseImage.StreamMetadata.Architectures
(the line computing archData, foundArch), add a nil check on
releaseImage.StreamMetadata and handle the nil case (e.g., skip this lookup, set
foundArch=false or use the alternative streams metadata lookup) to avoid
dereferencing a nil StreamMetadata; update any downstream logic that assumes
foundArch accordingly. Ensure references include releaseImage, StreamMetadata,
Architectures, archData/foundArch, hyperv1.ArchAliases and specifiedArch so the
change is applied exactly where the lookup happens.

In `@hypershift-operator/controllers/nodepool/azure.go`:
- Around line 124-139: The code dereferences nested fields without nil checks
causing panics; add explicit nil guards before accessing
releaseImage.StreamMetadata and before archData.RHCOS,
archData.RHCOS.Marketplace, archData.RHCOS.Marketplace.Azure and
archData.RHCOS.Marketplace.Azure.NoPurchasePlan; specifically, in the function
that reads releaseImage.StreamMetadata.Architectures and assigns
azureMarketplace := archData.RHCOS.Marketplace.Azure.NoPurchasePlan, first check
if releaseImage.StreamMetadata == nil (return nil,nil), then after finding
archData verify archData.RHCOS != nil && archData.RHCOS.Marketplace != nil &&
archData.RHCOS.Marketplace.Azure != nil &&
archData.RHCOS.Marketplace.Azure.NoPurchasePlan != nil (return nil,nil if any
are nil) before using azureMarketplace and its HyperVGen1/Gen2 fields.

In `@hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go`:
- Around line 47-56: Check for nil StreamMetadata and nil nested image structs
before dereferencing: guard that releaseImage.StreamMetadata is not nil before
indexing into StreamMetadata.Architectures[archName], and after finding arch,
ensure arch.Images and arch.Images.Kubevirt are non-nil before reading
DigestRef; if any are nil return a clear error (e.g., "no stream metadata", "no
image metadata for architecture %q", or "no kubevirt image metadata present in
release") so containerImage is only assigned when these checks pass.

In `@hypershift-operator/controllers/nodepool/openstack/openstack.go`:
- Around line 134-135: OpenstackDefaultImage currently dereferences
releaseImage.StreamMetadata without a nil check; add a guard at the start of
OpenstackDefaultImage to check if releaseImage == nil or
releaseImage.StreamMetadata == nil before accessing
StreamMetadata.Architectures, and if nil return a clear error (or fallback to
the alternative streams metadata if available) so the function never panics when
StreamMetadata is absent; reference the symbols OpenstackDefaultImage,
releaseImage, StreamMetadata, and Architectures when applying the check and
returning the error/fallback.

---

Nitpick comments:
In `@hypershift-operator/controllers/nodepool/aws.go`:
- Around line 424-453: getWindowsAMI currently reads releaseImage.StreamMetadata
directly so it ignores spec.osImageStream and cannot select stream-aware Windows
AMIs; change getWindowsAMI to accept an optional stream parameter (e.g., stream
...string) and use releaseImage.StreamMetadataForStream(stream) instead of
releaseImage.StreamMetadata, update callers (notably defaultNodePoolAMI and
places that resolve AMIs) to pass nodePool.Spec.OSImageStream.Name when present
so Windows AMI lookup mirrors the Linux/RHCOS stream-aware behavior.

In `@hypershift-operator/controllers/nodepool/azure.go`:
- Around line 122-167: getAzureMarketplaceMetadata currently reads
releaseImage.StreamMetadata directly and thus ignores spec.osImageStream; change
its signature to accept an optional stream parameter (e.g.,
getAzureMarketplaceMetadata(releaseImage *releaseinfo.ReleaseImage, arch string,
stream ...string)) and use releaseImage.StreamMetadataForStream(stream) (falling
back to existing StreamMetadata when no stream passed) instead of direct access
to releaseImage.StreamMetadata; update internal references (archData lookup,
azureMarketplace extraction) to use the returned stream metadata so Azure
respects Stream-aware image selection consistent with defaultNodePoolAMI and
supports dual-stream RHEL 9/10 scenarios.

In `@hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go`:
- Around line 39-67: The defaultImage function currently reads
releaseImage.StreamMetadata directly, which prevents honoring
spec.osImageStream; modify func defaultImage(nodePoolArch string, releaseImage
*releaseinfo.ReleaseImage, stream ...string) (string, string, error) to accept
an optional stream parameter, call releaseImage.StreamMetadataForStream(stream)
to obtain the appropriate StreamMetadata (falling back when stream is empty),
then use the returned metadata for the architecture lookup (arch, foundArch :=
streamMetadata.Architectures[archName]) and keep the existing validation and
return values unchanged; ensure callers of defaultImage are updated to pass the
optional stream where stream-aware selection is required.

In `@hypershift-operator/controllers/nodepool/openstack/openstack.go`:
- Around line 132-153: OpenstackDefaultImage currently reads
releaseImage.StreamMetadata directly and thus ignores spec.osImageStream; change
the function signature to accept an optional variadic stream parameter (e.g.,
OpenstackDefaultImage(releaseImage *releaseinfo.ReleaseImage, stream ...string))
and use releaseImage.StreamMetadataForStream(stream) to obtain the stream-aware
metadata before looking up Architectures["x86_64"], Artifacts["openstack"],
Formats["qcow2.gz"] and the disk; ensure the function otherwise returns the same
(disk.Location, disk.SHA256, error) and preserves existing error messages when
lookups fail.

In `@hypershift-operator/controllers/nodepool/powervs.go`:
- Around line 111-131: The getPowerVSImage function currently reads
releaseImage.StreamMetadata directly; change its signature to accept an optional
stream parameter (e.g., stream ...string) and obtain the architecture metadata
via releaseImage.StreamMetadataForStream(stream) instead of
releaseImage.StreamMetadata so PowerVS becomes stream-aware; update the function
body to use the returned StreamMetadata for the "ppc64le" lookup and keep the
rest of the logic unchanged, and then update all call sites of getPowerVSImage
to pass the stream when available.
🪄 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: c710fcdb-cc61-470d-bd2a-c5e6b808e139

📥 Commits

Reviewing files that changed from the base of the PR and between d912c4b and 6fee43e.

📒 Files selected for processing (30)
  • hypershift-operator/controllers/nodepool/aws.go
  • hypershift-operator/controllers/nodepool/aws_test.go
  • hypershift-operator/controllers/nodepool/azure.go
  • hypershift-operator/controllers/nodepool/azure_test.go
  • hypershift-operator/controllers/nodepool/conditions.go
  • hypershift-operator/controllers/nodepool/config.go
  • hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go
  • hypershift-operator/controllers/nodepool/kubevirt/kubevirt_test.go
  • hypershift-operator/controllers/nodepool/nodepool_controller.go
  • hypershift-operator/controllers/nodepool/nodepool_controller_test.go
  • hypershift-operator/controllers/nodepool/openstack/openstack.go
  • hypershift-operator/controllers/nodepool/openstack/openstack_test.go
  • hypershift-operator/controllers/nodepool/powervs.go
  • hypershift-operator/controllers/nodepool/powervs_test.go
  • hypershift-operator/controllers/nodepool/rhel_stream.go
  • hypershift-operator/controllers/nodepool/rhel_stream_test.go
  • hypershift-operator/controllers/nodepool/token.go
  • hypershift-operator/controllers/nodepool/version.go
  • hypershift-operator/controllers/nodepool/version_test.go
  • ignition-server/cmd/run_local_ignitionprovider.go
  • ignition-server/controllers/local_ignitionprovider.go
  • ignition-server/controllers/local_ignitionprovider_test.go
  • ignition-server/controllers/tokensecret_controller.go
  • ignition-server/controllers/tokensecret_controller_test.go
  • support/releaseinfo/deserialize.go
  • support/releaseinfo/deserialize_test.go
  • support/releaseinfo/registry_mirror_provider.go
  • support/releaseinfo/registryclient_provider.go
  • support/releaseinfo/releaseinfo.go
  • support/releaseinfo/releaseinfo_test.go
✅ Files skipped from review due to trivial changes (1)
  • support/releaseinfo/registry_mirror_provider.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • support/releaseinfo/registryclient_provider.go
  • ignition-server/controllers/tokensecret_controller.go
  • hypershift-operator/controllers/nodepool/version_test.go
  • hypershift-operator/controllers/nodepool/rhel_stream_test.go
  • ignition-server/controllers/tokensecret_controller_test.go
  • support/releaseinfo/deserialize_test.go
  • hypershift-operator/controllers/nodepool/token.go
  • hypershift-operator/controllers/nodepool/rhel_stream.go
  • hypershift-operator/controllers/nodepool/conditions.go
  • hypershift-operator/controllers/nodepool/nodepool_controller.go
  • ignition-server/controllers/local_ignitionprovider.go
  • support/releaseinfo/deserialize.go
  • support/releaseinfo/releaseinfo_test.go

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

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 (4)
hypershift-operator/controllers/nodepool/aws.go (1)

434-434: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing nil-safety guard for releaseImage.StreamMetadata.

Line 434 dereferences releaseImage.StreamMetadata.Architectures without checking if StreamMetadata is nil. When a payload uses only the new streams metadata format (without legacy single-stream), StreamMetadata can be nil, causing a panic.

This is the same blocking nil-safety issue the reviewer flagged for PowerVS and OpenStack.

🛡️ Add nil guard before accessing StreamMetadata
 func getWindowsAMI(region string, specifiedArch string, releaseImage *releaseinfo.ReleaseImage) (string, error) {
 	if releaseImage == nil {
 		return "", fmt.Errorf("release image is nil")
 	}
 
 	if releaseImage.StreamMetadata == nil {
-		return "", fmt.Errorf("release image stream metadata is nil")
+		return "", fmt.Errorf("release image stream metadata is nil")
 	}
 
 	archData, foundArch := releaseImage.StreamMetadata.Architectures[hyperv1.ArchAliases[specifiedArch]]
🤖 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 `@hypershift-operator/controllers/nodepool/aws.go` at line 434, Guard against
nil releaseImage.StreamMetadata before accessing Architectures: in the code path
using releaseImage.StreamMetadata.Architectures (the line computing archData,
foundArch), add a nil check on releaseImage.StreamMetadata and handle the nil
case (e.g., skip this lookup, set foundArch=false or use the alternative streams
metadata lookup) to avoid dereferencing a nil StreamMetadata; update any
downstream logic that assumes foundArch accordingly. Ensure references include
releaseImage, StreamMetadata, Architectures, archData/foundArch,
hyperv1.ArchAliases and specifiedArch so the change is applied exactly where the
lookup happens.
hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go (1)

47-56: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Multiple nil-safety risks in KubeVirt image resolution.

Two nil-dereference risks:

  1. Line 47: releaseImage.StreamMetadata.Architectures — if StreamMetadata is nil (multi-stream-only payload), this panics
  2. Line 53: arch.Images.Kubevirt.DigestRef — if arch.Images.Kubevirt is nil, accessing .DigestRef panics

These are the same blocking nil-safety issues flagged for PowerVS/OpenStack.

🛡️ Add nil guards before dereferencing
 func defaultImage(nodePoolArch string, releaseImage *releaseinfo.ReleaseImage) (string, string, error) {
+	if releaseImage.StreamMetadata == nil {
+		return "", "", fmt.Errorf("release image stream metadata is nil")
+	}
+
 	var archName string
 	switch nodePoolArch {
 	case hyperv1.ArchitectureS390X:
 		archName = hyperv1.ArchitectureS390X
 	default:
 		archName = hyperv1.ArchAliases[hyperv1.ArchitectureAMD64]
 	}
 	arch, foundArch := releaseImage.StreamMetadata.Architectures[archName]
 
 	if !foundArch {
 		return "", "", fmt.Errorf("couldn't find OS metadata for architecture %q", archName)
 	}
 
+	if arch.Images.Kubevirt == nil {
+		return "", "", fmt.Errorf("no kubevirt image metadata present in release")
+	}
 	if arch.Images.Kubevirt.DigestRef == "" {
 		return "", "", fmt.Errorf("no kubevirt image metadata present in release")
 	}
🤖 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 `@hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go` around lines
47 - 56, Check for nil StreamMetadata and nil nested image structs before
dereferencing: guard that releaseImage.StreamMetadata is not nil before indexing
into StreamMetadata.Architectures[archName], and after finding arch, ensure
arch.Images and arch.Images.Kubevirt are non-nil before reading DigestRef; if
any are nil return a clear error (e.g., "no stream metadata", "no image metadata
for architecture %q", or "no kubevirt image metadata present in release") so
containerImage is only assigned when these checks pass.
hypershift-operator/controllers/nodepool/openstack/openstack.go (1)

134-135: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing nil-safety guard for releaseImage.StreamMetadata (blocking issue).

Line 135 dereferences releaseImage.StreamMetadata.Architectures without checking if StreamMetadata is nil. When a payload uses only the new streams metadata format, StreamMetadata can be nil, causing a panic.

This is the blocking nil-safety issue the reviewer explicitly flagged for OpenStack in the PR objectives summary.

🛡️ Add nil guard before accessing StreamMetadata
 func OpenstackDefaultImage(releaseImage *releaseinfo.ReleaseImage) (string, string, error) {
+	if releaseImage.StreamMetadata == nil {
+		return "", "", fmt.Errorf("release image stream metadata is nil")
+	}
+
 	arch, foundArch := releaseImage.StreamMetadata.Architectures["x86_64"]
 	if !foundArch {
🤖 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 `@hypershift-operator/controllers/nodepool/openstack/openstack.go` around lines
134 - 135, OpenstackDefaultImage currently dereferences
releaseImage.StreamMetadata without a nil check; add a guard at the start of
OpenstackDefaultImage to check if releaseImage == nil or
releaseImage.StreamMetadata == nil before accessing
StreamMetadata.Architectures, and if nil return a clear error (or fallback to
the alternative streams metadata if available) so the function never panics when
StreamMetadata is absent; reference the symbols OpenstackDefaultImage,
releaseImage, StreamMetadata, and Architectures when applying the check and
returning the error/fallback.
hypershift-operator/controllers/nodepool/azure.go (1)

124-139: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Multiple nil-safety risks in marketplace metadata extraction.

Lines 128 and 136-137 dereference nested structs without nil-guarding intermediate fields:

  1. Line 128: releaseImage.StreamMetadata.Architectures — if StreamMetadata is nil (multi-stream-only payload), this panics
  2. Line 136: archData.RHCOS.Marketplace.Azure.NoPurchasePlan — if RHCOS.Marketplace or Marketplace.Azure is nil, this panics

These are the same blocking nil-safety issues flagged for PowerVS/OpenStack.

🛡️ Add nil guards for nested struct access
 func getAzureMarketplaceMetadata(releaseImage *releaseinfo.ReleaseImage, arch string) (*azureMarketplaceMetadata, error) {
 	if releaseImage.StreamMetadata == nil {
-		return nil, nil // No stream metadata available
+		return nil, nil
 	}
 
 	archData, foundArch := releaseImage.StreamMetadata.Architectures[arch]
 	if !foundArch {
 		return nil, fmt.Errorf("architecture %s not found in stream metadata", arch)
 	}
 
+	// Check for nil at each level of nested access
+	if archData.RHCOS.Marketplace == nil || archData.RHCOS.Marketplace.Azure == nil {
+		return nil, nil // No Azure marketplace data available
+	}
+
 	azureMarketplace := archData.RHCOS.Marketplace.Azure.NoPurchasePlan
 	if azureMarketplace.HyperVGen1 == nil && azureMarketplace.HyperVGen2 == nil {
🤖 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 `@hypershift-operator/controllers/nodepool/azure.go` around lines 124 - 139,
The code dereferences nested fields without nil checks causing panics; add
explicit nil guards before accessing releaseImage.StreamMetadata and before
archData.RHCOS, archData.RHCOS.Marketplace, archData.RHCOS.Marketplace.Azure and
archData.RHCOS.Marketplace.Azure.NoPurchasePlan; specifically, in the function
that reads releaseImage.StreamMetadata.Architectures and assigns
azureMarketplace := archData.RHCOS.Marketplace.Azure.NoPurchasePlan, first check
if releaseImage.StreamMetadata == nil (return nil,nil), then after finding
archData verify archData.RHCOS != nil && archData.RHCOS.Marketplace != nil &&
archData.RHCOS.Marketplace.Azure != nil &&
archData.RHCOS.Marketplace.Azure.NoPurchasePlan != nil (return nil,nil if any
are nil) before using azureMarketplace and its HyperVGen1/Gen2 fields.
🧹 Nitpick comments (5)
hypershift-operator/controllers/nodepool/powervs.go (1)

111-131: ⚡ Quick win

PowerVS doesn't support stream-aware image selection.

getPowerVSImage accesses releaseImage.StreamMetadata directly without accepting a stream parameter. This means PowerVS images can't respect spec.osImageStream for dual-stream RHEL 9/10 support.

For consistency with AWS's stream-aware pattern and to support multi-stream payloads, consider adding an optional stream ...string parameter and using releaseImage.StreamMetadataForStream(stream).

🤖 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 `@hypershift-operator/controllers/nodepool/powervs.go` around lines 111 - 131,
The getPowerVSImage function currently reads releaseImage.StreamMetadata
directly; change its signature to accept an optional stream parameter (e.g.,
stream ...string) and obtain the architecture metadata via
releaseImage.StreamMetadataForStream(stream) instead of
releaseImage.StreamMetadata so PowerVS becomes stream-aware; update the function
body to use the returned StreamMetadata for the "ppc64le" lookup and keep the
rest of the logic unchanged, and then update all call sites of getPowerVSImage
to pass the stream when available.
hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go (1)

39-67: ⚡ Quick win

KubeVirt doesn't support stream-aware image selection.

defaultImage accesses releaseImage.StreamMetadata directly without accepting a stream parameter. This means KubeVirt images can't respect spec.osImageStream for dual-stream RHEL 9/10 support.

For consistency with AWS and to support multi-stream payloads, consider adding an optional stream ...string parameter and using releaseImage.StreamMetadataForStream(stream).

🤖 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 `@hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go` around lines
39 - 67, The defaultImage function currently reads releaseImage.StreamMetadata
directly, which prevents honoring spec.osImageStream; modify func
defaultImage(nodePoolArch string, releaseImage *releaseinfo.ReleaseImage, stream
...string) (string, string, error) to accept an optional stream parameter, call
releaseImage.StreamMetadataForStream(stream) to obtain the appropriate
StreamMetadata (falling back when stream is empty), then use the returned
metadata for the architecture lookup (arch, foundArch :=
streamMetadata.Architectures[archName]) and keep the existing validation and
return values unchanged; ensure callers of defaultImage are updated to pass the
optional stream where stream-aware selection is required.
hypershift-operator/controllers/nodepool/openstack/openstack.go (1)

132-153: ⚡ Quick win

OpenStack doesn't support stream-aware image selection.

OpenstackDefaultImage accesses releaseImage.StreamMetadata directly without accepting a stream parameter. This means OpenStack images can't respect spec.osImageStream for dual-stream RHEL 9/10 support, unlike AWS which correctly uses stream-aware selection.

For consistency with AWS and to support multi-stream payloads, consider adding an optional stream ...string parameter and using releaseImage.StreamMetadataForStream(stream).

🤖 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 `@hypershift-operator/controllers/nodepool/openstack/openstack.go` around lines
132 - 153, OpenstackDefaultImage currently reads releaseImage.StreamMetadata
directly and thus ignores spec.osImageStream; change the function signature to
accept an optional variadic stream parameter (e.g.,
OpenstackDefaultImage(releaseImage *releaseinfo.ReleaseImage, stream ...string))
and use releaseImage.StreamMetadataForStream(stream) to obtain the stream-aware
metadata before looking up Architectures["x86_64"], Artifacts["openstack"],
Formats["qcow2.gz"] and the disk; ensure the function otherwise returns the same
(disk.Location, disk.SHA256, error) and preserves existing error messages when
lookups fail.
hypershift-operator/controllers/nodepool/aws.go (1)

424-453: ⚡ Quick win

Windows AMI path doesn't support stream-aware image selection.

getWindowsAMI accesses releaseImage.StreamMetadata directly and doesn't accept a stream parameter, unlike the Linux/RHCOS path which correctly passes nodePool.Spec.OSImageStream.Name to defaultNodePoolAMI (lines 137, 364). This means Windows AMIs can't respect spec.osImageStream for dual-stream RHEL 9/10 support.

For consistency with the Linux path and to properly support multi-stream payloads, consider:

  1. Adding an optional stream ...string parameter to getWindowsAMI
  2. Using releaseImage.StreamMetadataForStream(stream) instead of direct StreamMetadata access
  3. Passing nodePool.Spec.OSImageStream.Name from callers (lines 343, 130)
🤖 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 `@hypershift-operator/controllers/nodepool/aws.go` around lines 424 - 453,
getWindowsAMI currently reads releaseImage.StreamMetadata directly so it ignores
spec.osImageStream and cannot select stream-aware Windows AMIs; change
getWindowsAMI to accept an optional stream parameter (e.g., stream ...string)
and use releaseImage.StreamMetadataForStream(stream) instead of
releaseImage.StreamMetadata, update callers (notably defaultNodePoolAMI and
places that resolve AMIs) to pass nodePool.Spec.OSImageStream.Name when present
so Windows AMI lookup mirrors the Linux/RHCOS stream-aware behavior.
hypershift-operator/controllers/nodepool/azure.go (1)

122-167: ⚡ Quick win

Azure marketplace doesn't support stream-aware image selection.

getAzureMarketplaceMetadata accesses releaseImage.StreamMetadata directly without accepting a stream parameter, unlike AWS's defaultNodePoolAMI which correctly uses StreamMetadataForStream(). This means Azure can't respect spec.osImageStream for dual-stream RHEL 9/10 support.

For consistency and to properly support multi-stream payloads, consider adding an optional stream ...string parameter and using releaseImage.StreamMetadataForStream(stream).

🤖 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 `@hypershift-operator/controllers/nodepool/azure.go` around lines 122 - 167,
getAzureMarketplaceMetadata currently reads releaseImage.StreamMetadata directly
and thus ignores spec.osImageStream; change its signature to accept an optional
stream parameter (e.g., getAzureMarketplaceMetadata(releaseImage
*releaseinfo.ReleaseImage, arch string, stream ...string)) and use
releaseImage.StreamMetadataForStream(stream) (falling back to existing
StreamMetadata when no stream passed) instead of direct access to
releaseImage.StreamMetadata; update internal references (archData lookup,
azureMarketplace extraction) to use the returned stream metadata so Azure
respects Stream-aware image selection consistent with defaultNodePoolAMI and
supports dual-stream RHEL 9/10 scenarios.
🤖 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 `@hypershift-operator/controllers/nodepool/aws_test.go`:
- Around line 716-733: The test case named "When RHELCoreOSExtensions is nil, it
should return error" has an expectedError string that doesn't match the actual
error returned by getWindowsAMI; update the test's expectedError to match the
production error path (e.g., the "no aws-winli regions data found..." message)
or change the fixture to trigger the rhel-coreos-extensions error if that's the
intended behavior; locate the test struct in aws_test.go (the case with name
matching the above), and set expectedError to the exact error returned by
getWindowsAMI for that input shape (or adjust the releaseImage/StreamMetadata so
getWindowsAMI hits the rhel-coreos-extensions branch).

In `@hypershift-operator/controllers/nodepool/powervs_test.go`:
- Around line 11-52: Add a test case to TestGetPowerVSImage that covers
releaseImage == nil and releaseImage.StreamMetadata == nil to prevent the
nil-dereference panic, and update getPowerVSImage to defensively check for nil
releaseImage and nil StreamMetadata at the top (returning a clear error like
"release image metadata is nil" or similar) before accessing
StreamMetadata.Architectures; reference getPowerVSImage in your patch and ensure
the new tests assert an error containing the new message.

In `@hypershift-operator/controllers/nodepool/powervs.go`:
- Around line 117-119: Add a nil-check for releaseImage.StreamMetadata before
dereferencing it: ensure releaseImage.StreamMetadata != nil before accessing
releaseImage.StreamMetadata.Architectures (the code that constructs arch and
later checks arch.Images.PowerVS.Regions). If StreamMetadata is nil, return an
appropriate error (similar to the existing error path) so the function does not
panic; reference releaseImage, StreamMetadata, Architectures, arch, Images,
PowerVS, and Regions when locating where to insert this guard.

In `@ignition-server/cmd/run_local_ignitionprovider.go`:
- Line 114: The call to GetPayload is ignoring the CLI --feature-gate-manifest
because the fifth argument is a literal empty string; update the GetPayload call
so it passes the stored option o.FeatureGateManifest (the same value previously
set on the provider) instead of the empty string. Locate the call to
p.GetPayload(...) and replace the 5th parameter with o.FeatureGateManifest so
the feature-gate manifest flag is forwarded to GetPayload.

---

Outside diff comments:
In `@hypershift-operator/controllers/nodepool/aws.go`:
- Line 434: Guard against nil releaseImage.StreamMetadata before accessing
Architectures: in the code path using releaseImage.StreamMetadata.Architectures
(the line computing archData, foundArch), add a nil check on
releaseImage.StreamMetadata and handle the nil case (e.g., skip this lookup, set
foundArch=false or use the alternative streams metadata lookup) to avoid
dereferencing a nil StreamMetadata; update any downstream logic that assumes
foundArch accordingly. Ensure references include releaseImage, StreamMetadata,
Architectures, archData/foundArch, hyperv1.ArchAliases and specifiedArch so the
change is applied exactly where the lookup happens.

In `@hypershift-operator/controllers/nodepool/azure.go`:
- Around line 124-139: The code dereferences nested fields without nil checks
causing panics; add explicit nil guards before accessing
releaseImage.StreamMetadata and before archData.RHCOS,
archData.RHCOS.Marketplace, archData.RHCOS.Marketplace.Azure and
archData.RHCOS.Marketplace.Azure.NoPurchasePlan; specifically, in the function
that reads releaseImage.StreamMetadata.Architectures and assigns
azureMarketplace := archData.RHCOS.Marketplace.Azure.NoPurchasePlan, first check
if releaseImage.StreamMetadata == nil (return nil,nil), then after finding
archData verify archData.RHCOS != nil && archData.RHCOS.Marketplace != nil &&
archData.RHCOS.Marketplace.Azure != nil &&
archData.RHCOS.Marketplace.Azure.NoPurchasePlan != nil (return nil,nil if any
are nil) before using azureMarketplace and its HyperVGen1/Gen2 fields.

In `@hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go`:
- Around line 47-56: Check for nil StreamMetadata and nil nested image structs
before dereferencing: guard that releaseImage.StreamMetadata is not nil before
indexing into StreamMetadata.Architectures[archName], and after finding arch,
ensure arch.Images and arch.Images.Kubevirt are non-nil before reading
DigestRef; if any are nil return a clear error (e.g., "no stream metadata", "no
image metadata for architecture %q", or "no kubevirt image metadata present in
release") so containerImage is only assigned when these checks pass.

In `@hypershift-operator/controllers/nodepool/openstack/openstack.go`:
- Around line 134-135: OpenstackDefaultImage currently dereferences
releaseImage.StreamMetadata without a nil check; add a guard at the start of
OpenstackDefaultImage to check if releaseImage == nil or
releaseImage.StreamMetadata == nil before accessing
StreamMetadata.Architectures, and if nil return a clear error (or fallback to
the alternative streams metadata if available) so the function never panics when
StreamMetadata is absent; reference the symbols OpenstackDefaultImage,
releaseImage, StreamMetadata, and Architectures when applying the check and
returning the error/fallback.

---

Nitpick comments:
In `@hypershift-operator/controllers/nodepool/aws.go`:
- Around line 424-453: getWindowsAMI currently reads releaseImage.StreamMetadata
directly so it ignores spec.osImageStream and cannot select stream-aware Windows
AMIs; change getWindowsAMI to accept an optional stream parameter (e.g., stream
...string) and use releaseImage.StreamMetadataForStream(stream) instead of
releaseImage.StreamMetadata, update callers (notably defaultNodePoolAMI and
places that resolve AMIs) to pass nodePool.Spec.OSImageStream.Name when present
so Windows AMI lookup mirrors the Linux/RHCOS stream-aware behavior.

In `@hypershift-operator/controllers/nodepool/azure.go`:
- Around line 122-167: getAzureMarketplaceMetadata currently reads
releaseImage.StreamMetadata directly and thus ignores spec.osImageStream; change
its signature to accept an optional stream parameter (e.g.,
getAzureMarketplaceMetadata(releaseImage *releaseinfo.ReleaseImage, arch string,
stream ...string)) and use releaseImage.StreamMetadataForStream(stream) (falling
back to existing StreamMetadata when no stream passed) instead of direct access
to releaseImage.StreamMetadata; update internal references (archData lookup,
azureMarketplace extraction) to use the returned stream metadata so Azure
respects Stream-aware image selection consistent with defaultNodePoolAMI and
supports dual-stream RHEL 9/10 scenarios.

In `@hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go`:
- Around line 39-67: The defaultImage function currently reads
releaseImage.StreamMetadata directly, which prevents honoring
spec.osImageStream; modify func defaultImage(nodePoolArch string, releaseImage
*releaseinfo.ReleaseImage, stream ...string) (string, string, error) to accept
an optional stream parameter, call releaseImage.StreamMetadataForStream(stream)
to obtain the appropriate StreamMetadata (falling back when stream is empty),
then use the returned metadata for the architecture lookup (arch, foundArch :=
streamMetadata.Architectures[archName]) and keep the existing validation and
return values unchanged; ensure callers of defaultImage are updated to pass the
optional stream where stream-aware selection is required.

In `@hypershift-operator/controllers/nodepool/openstack/openstack.go`:
- Around line 132-153: OpenstackDefaultImage currently reads
releaseImage.StreamMetadata directly and thus ignores spec.osImageStream; change
the function signature to accept an optional variadic stream parameter (e.g.,
OpenstackDefaultImage(releaseImage *releaseinfo.ReleaseImage, stream ...string))
and use releaseImage.StreamMetadataForStream(stream) to obtain the stream-aware
metadata before looking up Architectures["x86_64"], Artifacts["openstack"],
Formats["qcow2.gz"] and the disk; ensure the function otherwise returns the same
(disk.Location, disk.SHA256, error) and preserves existing error messages when
lookups fail.

In `@hypershift-operator/controllers/nodepool/powervs.go`:
- Around line 111-131: The getPowerVSImage function currently reads
releaseImage.StreamMetadata directly; change its signature to accept an optional
stream parameter (e.g., stream ...string) and obtain the architecture metadata
via releaseImage.StreamMetadataForStream(stream) instead of
releaseImage.StreamMetadata so PowerVS becomes stream-aware; update the function
body to use the returned StreamMetadata for the "ppc64le" lookup and keep the
rest of the logic unchanged, and then update all call sites of getPowerVSImage
to pass the stream when available.
🪄 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: c710fcdb-cc61-470d-bd2a-c5e6b808e139

📥 Commits

Reviewing files that changed from the base of the PR and between d912c4b and 6fee43e.

📒 Files selected for processing (30)
  • hypershift-operator/controllers/nodepool/aws.go
  • hypershift-operator/controllers/nodepool/aws_test.go
  • hypershift-operator/controllers/nodepool/azure.go
  • hypershift-operator/controllers/nodepool/azure_test.go
  • hypershift-operator/controllers/nodepool/conditions.go
  • hypershift-operator/controllers/nodepool/config.go
  • hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go
  • hypershift-operator/controllers/nodepool/kubevirt/kubevirt_test.go
  • hypershift-operator/controllers/nodepool/nodepool_controller.go
  • hypershift-operator/controllers/nodepool/nodepool_controller_test.go
  • hypershift-operator/controllers/nodepool/openstack/openstack.go
  • hypershift-operator/controllers/nodepool/openstack/openstack_test.go
  • hypershift-operator/controllers/nodepool/powervs.go
  • hypershift-operator/controllers/nodepool/powervs_test.go
  • hypershift-operator/controllers/nodepool/rhel_stream.go
  • hypershift-operator/controllers/nodepool/rhel_stream_test.go
  • hypershift-operator/controllers/nodepool/token.go
  • hypershift-operator/controllers/nodepool/version.go
  • hypershift-operator/controllers/nodepool/version_test.go
  • ignition-server/cmd/run_local_ignitionprovider.go
  • ignition-server/controllers/local_ignitionprovider.go
  • ignition-server/controllers/local_ignitionprovider_test.go
  • ignition-server/controllers/tokensecret_controller.go
  • ignition-server/controllers/tokensecret_controller_test.go
  • support/releaseinfo/deserialize.go
  • support/releaseinfo/deserialize_test.go
  • support/releaseinfo/registry_mirror_provider.go
  • support/releaseinfo/registryclient_provider.go
  • support/releaseinfo/releaseinfo.go
  • support/releaseinfo/releaseinfo_test.go
✅ Files skipped from review due to trivial changes (1)
  • support/releaseinfo/registry_mirror_provider.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • support/releaseinfo/registryclient_provider.go
  • ignition-server/controllers/tokensecret_controller.go
  • hypershift-operator/controllers/nodepool/version_test.go
  • hypershift-operator/controllers/nodepool/rhel_stream_test.go
  • ignition-server/controllers/tokensecret_controller_test.go
  • support/releaseinfo/deserialize_test.go
  • hypershift-operator/controllers/nodepool/token.go
  • hypershift-operator/controllers/nodepool/rhel_stream.go
  • hypershift-operator/controllers/nodepool/conditions.go
  • hypershift-operator/controllers/nodepool/nodepool_controller.go
  • ignition-server/controllers/local_ignitionprovider.go
  • support/releaseinfo/deserialize.go
  • support/releaseinfo/releaseinfo_test.go
🛑 Comments failed to post (4)
hypershift-operator/controllers/nodepool/aws_test.go (1)

716-733: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Expected error assertion does not match the current getWindowsAMI contract.

At Line 732, the expected error string (no rhel-coreos-extensions data found...) doesn’t match the production path shown in hypershift-operator/controllers/nodepool/aws.go (which returns no aws-winli regions data found... for this fixture shape). This will make the case fail for the wrong reason.

Suggested fix
-		{
-			name:   "When RHELCoreOSExtensions is nil, it should return error",
+		{
+			name:   "When aws-winli regions are missing, it should return error",
 			region: "us-east-1",
 			arch:   hyperv1.ArchitectureAMD64,
 			releaseImage: &releaseinfo.ReleaseImage{
 				ImageStream: &v1.ImageStream{
 					ObjectMeta: metav1.ObjectMeta{
 						Name: "4.17.0",
 					},
 				},
 				StreamMetadata: &releaseinfo.CoreOSStreamMetadata{
 					Architectures: map[string]releaseinfo.CoreOSArchitecture{
 						"x86_64": {},
 					},
 				},
 			},
-			expectedError: "no rhel-coreos-extensions data found in release image metadata",
+			expectedError: "no aws-winli regions data found in release image metadata",
 		},
📝 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.

		{
			name:   "When aws-winli regions are missing, it should return error",
			region: "us-east-1",
			arch:   hyperv1.ArchitectureAMD64,
			releaseImage: &releaseinfo.ReleaseImage{
				ImageStream: &v1.ImageStream{
					ObjectMeta: metav1.ObjectMeta{
						Name: "4.17.0",
					},
				},
				StreamMetadata: &releaseinfo.CoreOSStreamMetadata{
					Architectures: map[string]releaseinfo.CoreOSArchitecture{
						"x86_64": {},
					},
				},
			},
			expectedError: "no aws-winli regions data found in release image metadata",
		},
🤖 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 `@hypershift-operator/controllers/nodepool/aws_test.go` around lines 716 - 733,
The test case named "When RHELCoreOSExtensions is nil, it should return error"
has an expectedError string that doesn't match the actual error returned by
getWindowsAMI; update the test's expectedError to match the production error
path (e.g., the "no aws-winli regions data found..." message) or change the
fixture to trigger the rhel-coreos-extensions error if that's the intended
behavior; locate the test struct in aws_test.go (the case with name matching the
above), and set expectedError to the exact error returned by getWindowsAMI for
that input shape (or adjust the releaseImage/StreamMetadata so getWindowsAMI
hits the rhel-coreos-extensions branch).
hypershift-operator/controllers/nodepool/powervs_test.go (1)

11-52: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Nil metadata panic path is still untested (and likely still reachable).

Line 47 only exercises map-lookup failures. It does not cover releaseImage == nil / releaseImage.StreamMetadata == nil, while getPowerVSImage currently dereferences releaseImage.StreamMetadata directly. Add this case so the panic path is caught and fixed.

Suggested test addition
 	{
 		name:   "When architecture is not found, it should return error",
 		region: "us-south",
 		releaseImage: &releaseinfo.ReleaseImage{
 			StreamMetadata: &releaseinfo.CoreOSStreamMetadata{
 				Architectures: map[string]releaseinfo.CoreOSArchitecture{},
 			},
 		},
 		expectedError: "couldn't find OS metadata for architecture",
 	},
+	{
+		name:   "When stream metadata is nil, it should return error",
+		region: "us-south",
+		releaseImage: &releaseinfo.ReleaseImage{
+			StreamMetadata: nil,
+		},
+		expectedError: "release image stream metadata is nil",
+	},
🤖 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 `@hypershift-operator/controllers/nodepool/powervs_test.go` around lines 11 -
52, Add a test case to TestGetPowerVSImage that covers releaseImage == nil and
releaseImage.StreamMetadata == nil to prevent the nil-dereference panic, and
update getPowerVSImage to defensively check for nil releaseImage and nil
StreamMetadata at the top (returning a clear error like "release image metadata
is nil" or similar) before accessing StreamMetadata.Architectures; reference
getPowerVSImage in your patch and ensure the new tests assert an error
containing the new message.
hypershift-operator/controllers/nodepool/powervs.go (1)

117-119: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Partial fix: Regions nil-guard added, but root StreamMetadata nil-safety still missing.

The added nil check for arch.Images.PowerVS.Regions is good, but line 112 still dereferences releaseImage.StreamMetadata.Architectures without guarding StreamMetadata itself. If a payload uses only streams (not legacy stream), StreamMetadata can be nil, causing a panic before reaching the Regions check.

This is the primary blocking nil-safety issue the reviewer flagged in the PR objectives: "Nil-safety panic risk: powervs.go accesses releaseImage.StreamMetadata directly; if StreamMetadata is nil (e.g., payload uses only streams), this can panic."

🛡️ Add StreamMetadata nil guard before line 112
 func getPowerVSImage(region string, releaseImage *releaseinfo.ReleaseImage) (*releaseinfo.CoreOSPowerVSImage, string, error) {
+	if releaseImage.StreamMetadata == nil {
+		return nil, "", fmt.Errorf("release image stream metadata is nil")
+	}
+
 	arch, foundArch := releaseImage.StreamMetadata.Architectures["ppc64le"]
 	if !foundArch {
🤖 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 `@hypershift-operator/controllers/nodepool/powervs.go` around lines 117 - 119,
Add a nil-check for releaseImage.StreamMetadata before dereferencing it: ensure
releaseImage.StreamMetadata != nil before accessing
releaseImage.StreamMetadata.Architectures (the code that constructs arch and
later checks arch.Images.PowerVS.Regions). If StreamMetadata is nil, return an
appropriate error (similar to the existing error path) so the function does not
panic; reference releaseImage, StreamMetadata, Architectures, arch, Images,
PowerVS, and Regions when locating where to insert this guard.
ignition-server/cmd/run_local_ignitionprovider.go (1)

114-114: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

The --feature-gate-manifest flag is silently ignored.

Line 114 passes "" as the 5th parameter (fgManifest) to GetPayload, but it should pass o.FeatureGateManifest. The CLI accepts a --feature-gate-manifest flag (line 46), stores it in o.FeatureGateManifest, and even sets it on the provider struct (line 111), yet the value is never forwarded to GetPayload.

🐛 Proposed fix
-	payload, err := p.GetPayload(ctx, o.Image, config.String(), "", "", "", "")
+	payload, err := p.GetPayload(ctx, o.Image, config.String(), "", "", o.FeatureGateManifest, "")
📝 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.

	payload, err := p.GetPayload(ctx, o.Image, config.String(), "", "", o.FeatureGateManifest, "")
🤖 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 `@ignition-server/cmd/run_local_ignitionprovider.go` at line 114, The call to
GetPayload is ignoring the CLI --feature-gate-manifest because the fifth
argument is a literal empty string; update the GetPayload call so it passes the
stored option o.FeatureGateManifest (the same value previously set on the
provider) instead of the empty string. Locate the call to p.GetPayload(...) and
replace the 5th parameter with o.FeatureGateManifest so the feature-gate
manifest flag is forwarded to GetPayload.

@hypershift-jira-solve-ci

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

Copy link
Copy Markdown
Author

Now I have the complete picture. Let me compile the final report.

Test Failure Analysis Complete

Job Information

  • Prow Job: test / Unit Tests (hypershift-operator) → cascading to codecov/patch and codecov/project
  • Build ID: GitHub Actions run 27365633079 (check runs 80867216177 and 80867213371)
  • PR: #8714 — DNM: CNTRLPLANE-3612: Add dual-stream RHEL 9/10 NodePool support
  • Repository: openshift/hypershift (fork: hypershift-community/hypershift, branch: fix-CNTRLPLANE-3612)

Test Failure Analysis

Error

--- FAIL: TestGetWindowsAMI (0.00s)
    --- FAIL: TestGetWindowsAMI/When_RHELCoreOSExtensions_is_nil,_it_should_return_error (0.00s)
        aws_test.go:913: expected error containing "no rhel-coreos-extensions data found in release image metadata",
                         but got "no aws-winli regions data found in release image metadata"
FAIL	github.com/openshift/hypershift/hypershift-operator/controllers/nodepool	7.822s

Summary

The unit test TestGetWindowsAMI/When_RHELCoreOSExtensions_is_nil fails because the PR refactored getWindowsAMI() to use a new flattened CoreOSArchitecture struct (replacing the external stream-metadata-go types), which collapsed the previous two-level nil check (RHELCoreOSExtensions == nil then AwsWinLi == nil) into a single check (archData.RHCOS.AWSWinLi.Regions == nil). This eliminated a previously distinct error path, but the test still expects the old error message "no rhel-coreos-extensions data found in release image metadata" for the nil RHELCoreOSExtensions case. Since Go zero-values the RHCOS and AWSWinLi structs (they are no longer pointers), both the "nil RHELCoreOSExtensions" and "nil AwsWinLi" cases now hit the same code path producing "no aws-winli regions data found in release image metadata". The test's expected error string was not updated to match. The cascading codecov/patch (32.20% vs target 41.59%) and codecov/project (41.57%, -0.03%) failures are a direct consequence: the unit test failure in the hypershift-operator shard causes the "Upload to Codecov" step to be skipped, resulting in incomplete coverage data and threshold violations.

Root Cause

The PR replaces the external github.com/coreos/stream-metadata-go types (stream.Stream, stream.Arch, rhcos.Extensions, rhcos.ReplicatedImage) with new internal types (releaseinfo.CoreOSStreamMetadata, releaseinfo.CoreOSArchitecture, releaseinfo.CoreRHCOSImage, releaseinfo.CoreAWSWinLi).

The critical structural change is:

  • Before: archData.RHELCoreOSExtensions was a pointer (*rhcos.Extensions), allowing a distinct nil check before accessing AwsWinLi. The old code had two separate error paths:

    1. RHELCoreOSExtensions == nil"no rhel-coreos-extensions data found in release image metadata"
    2. RHELCoreOSExtensions.AwsWinLi == nil"no aws-winli regions data found in release image metadata"
  • After: archData.RHCOS is a value type (CoreRHCOSImage), and AWSWinLi is also a value type (CoreAWSWinLi). Neither can be nil. The new code collapses to a single check: archData.RHCOS.AWSWinLi.Regions == nil"no aws-winli regions data found in release image metadata".

The test at aws_test.go:717-732 ("When RHELCoreOSExtensions is nil, it should return error") constructs architecture data with "x86_64": {} (zero-valued CoreOSArchitecture), which means RHCOS, AWSWinLi, and Regions are all zero values. It then expects the error "no rhel-coreos-extensions data found in release image metadata" — but that error message was removed from the production code. The actual error is now "no aws-winli regions data found in release image metadata".

The codecov/patch and codecov/project failures are cascading consequences: when test / Unit Tests (hypershift-operator) fails, the subsequent "Upload to Codecov" step is skipped (confirmed in the job step results), so the hypershift-operator coverage flag has no data for this run, pulling overall coverage below thresholds.

Recommendations
  1. Fix the test expectation (aws_test.go:732): Update the expectedError string from "no rhel-coreos-extensions data found in release image metadata" to "no aws-winli regions data found in release image metadata", since both the "nil RHELCoreOSExtensions" and "nil AwsWinLi" cases now produce the same error with value-type structs.

  2. Alternatively, merge the two test cases: Since the "When RHELCoreOSExtensions is nil" and "When AwsWinLi is nil" cases now test identical behavior (both produce "no aws-winli regions data found" because zero-valued structs make the two scenarios indistinguishable), consider merging them into a single test case or renaming the first to reflect the new behavior (e.g., "When RHCOS and AWSWinLi are zero-valued, it should return error").

  3. Improve patch coverage (32.20% vs 41.59% target): The codecov report identifies 40 uncovered lines in the diff, concentrated in support/releaseinfo/deserialize.go (12 missing), support/releaseinfo/testutils/testutils.go (10 missing), ignition-server/controllers/local_ignitionprovider.go (6 missing), support/releaseinfo/releaseinfo.go (6 missing), and support/releaseinfo/registryclient_provider.go (4 missing). Adding unit tests for the new CoreOSStreamMetadata deserialization and ReleaseImage helper functions would address both the failing test and the coverage gap.

Evidence
Evidence Detail
Failing test TestGetWindowsAMI/When_RHELCoreOSExtensions_is_nil,_it_should_return_error at aws_test.go:913
Expected error "no rhel-coreos-extensions data found in release image metadata"
Actual error "no aws-winli regions data found in release image metadata"
Root cause PR changed RHELCoreOSExtensions from pointer to value type, collapsing two nil-check error paths into one
Old code (removed) if archData.RHELCoreOSExtensions == nil { return "", fmt.Errorf("no rhel-coreos-extensions data found...") }
New code if archData.RHCOS.AWSWinLi.Regions == nil { return "", fmt.Errorf("no aws-winli regions data found...") }
Test data (aws_test.go:726-728) CoreOSArchitecture{} — zero-valued struct means RHCOS.AWSWinLi.Regions is nil
Cascade: Upload to Codecov Step status: skipped (due to unit test failure)
codecov/patch 32.20% of diff hit (target: 41.59%) — below threshold
codecov/project 41.57% (-0.03%) compared to base 2f6b004
Files with missing coverage deserialize.go (12 lines), testutils.go (10), local_ignitionprovider.go (6), releaseinfo.go (6), registryclient_provider.go (4)
Dependency removed github.com/coreos/stream-metadata-go v0.4.11 replaced with internal types

OpenShift CI Bot and others added 5 commits June 11, 2026 18:25
Starting in OCP 5.0, the coreos-bootimages ConfigMap contains
a "streams" key alongside the legacy "stream" key, with per-stream
boot image metadata for RHEL 9 and RHEL 10. This change:

- Adds DeserializeImageMetadataMultiStream to parse both formats
- Adds StreamsMetadata field to ReleaseImage for multi-stream data
- Adds StreamMetadataForStream helper for stream-specific lookups
- Updates RegistryClientProvider to populate both metadata fields

The legacy DeserializeImageMetadata is kept for backward compatibility.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Implements per-NodePool RHEL stream selection via spec.osImageStream,
enabling mixed RHEL 9 and RHEL 10 workers in a HostedCluster.

Key changes:
- getRHELStream() resolves the effective stream from spec, release
  version, and runc usage with proper validation and fallback logic
- Explicit stream included in config hash to trigger rollouts; implicit
  defaults excluded to prevent fleet-wide mass rollouts on upgrade
- Token secret carries os-stream key for ignition payload generation
- Boot image resolution (AMI, GCP) supports stream-specific metadata
- validMachineConfigCondition validates stream/version/runc constraints
- status.osImageStream inferred from node RHCOS version strings
- runc guard prevents RHEL 10 selection when runc configs are present

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Extends the ignition server to generate stream-specific ignition
payloads by:

- Adding osStream parameter to IgnitionProvider.GetPayload interface
- Reading os-stream from token secret in TokenSecretReconciler
- Generating 99_osimagestream.yaml CR in the MCC manifest directory
  between runMCO and runMCC, so MCC bootstrap discovers the requested
  stream via OCI labels and overrides baseOSContainerImage accordingly
- Extracting resolveClusterConfigComponent to reduce GetPayload
  cyclomatic complexity

When osStream is empty (legacy payloads < 5.0), no OSImageStream CR
is generated and MCC uses the default BaseOSContainerImage.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Covers the full dual-stream implementation:
- getRHELStream decision matrix (explicit/implicit streams, runc guard,
  version constraints) with 12 test cases
- DeserializeImageMetadataMultiStream for legacy and multi-stream
  ConfigMap formats
- inferOSStreamFromNodeInfo RHCOS version string parsing
- writeOSImageStreamCR YAML generation including empty-stream no-op
- Updated fakeIgnitionProvider to match new GetPayload signature

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add the Claude Code skill definition for the jira:solve command
that automates JIRA issue resolution.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@openshift-ci openshift-ci Bot added the area/ai Indicates the PR includes changes related to AI - Claude agents, Cursor rules, etc. label Jun 11, 2026
@openshift-ci

openshift-ci Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: hypershift-jira-solve-ci[bot]
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

@openshift-ci

openshift-ci Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

@hypershift-jira-solve-ci[bot]: 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.

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Author

Re: #8714 (comment)

Done.

  1. Rebased the branch onto upstream/main — all 5 commits rebased cleanly with no conflicts.
  2. Created tmp.txt in the root folder with literal content test.

AI-assisted response via Claude Code

@enxebre enxebre closed this Jun 11, 2026
@enxebre enxebre deleted the fix-CNTRLPLANE-3612 branch June 11, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ai Indicates the PR includes changes related to AI - Claude agents, Cursor rules, etc. area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/platform/aws PR/issue for AWS (AWSPlatform) platform area/platform/azure PR/issue for Azure (AzurePlatform) platform area/platform/gcp PR/issue for GCP (GCPPlatform) platform area/platform/kubevirt PR/issue for KubeVirt (KubevirtPlatform) platform area/platform/openstack PR/issue for OpenStack (OpenStackPlatform) platform area/platform/powervs PR/issue for PowerVS (PowerVSPlatform) platform do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. 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