Skip to content

METAL-1730: Add stream selection for multi-version support#175

Open
honza wants to merge 1 commit into
openshift:mainfrom
honza:dual-stream
Open

METAL-1730: Add stream selection for multi-version support#175
honza wants to merge 1 commit into
openshift:mainfrom
honza:dual-stream

Conversation

@honza
Copy link
Copy Markdown
Member

@honza honza commented Apr 14, 2026

Enable the image-customization-controller to serve different CoreOS base images (e.g., RHCOS 9 vs RHCOS 10) based on a stream label on PreprovisioningImage resources. Images are now indexed by (stream, architecture) and discovered from stream-prefixed filenames like ironic-python-agent-rhel-9.iso. The coreos.openshift.io/stream label is read from PPI metadata and falls back to the default stream when absent.

How it works

The coreos.openshift.io/stream label on a BareMetalHost determines
which kernel, initrd, and rootfs files are used during PXE boot. For
example, a BMH labeled coreos.openshift.io/stream: rhel-10 will PXE
boot with ironic-python-agent-rhel-10.kernel,
ironic-python-agent-rhel-10.initramfs, and
ironic-python-agent-rhel-10.rootfs instead of the unqualified defaults.

The per-node coreos.live.rootfs_url kernel parameter set via ICC's
ExtraKernelParams overrides the global one from ironic-image because
dracut's getarg returns the last value for duplicate parameters.

Changes across repos

installer (openshift/installer#10502)
pkg/asset/machines/baremetal/: Sets the coreos.openshift.io/stream
label on all generated BareMetalHost objects (control-plane, worker,
arbiter) based on the install-config's OSImageStream setting, defaulting
to rhel-9. Configures hostSelector.matchLabels on
BareMetalMachineProviderSpec so MachineSets only claim BMHs with the
matching stream label.

cluster-baremetal-operator (openshift/cluster-baremetal-operator#590)
provisioning/image_customization.go: Passes three new environment
variables to the ICC container: DEPLOY_KERNEL (path to the kernel file),
IMAGE_SHARED_DIR (path to the shared images directory where
stream-prefixed files are discovered), and IRONIC_ROOTFS_URL (base URL
for the rootfs served by httpd). These enable ICC to discover multi-stream
images and construct per-node kernel/rootfs URLs.

machine-os-images (openshift/machine-os-images#83)
scripts/copy-metal: Creates stream-prefixed IPA symlinks (e.g.
ironic-python-agent-rhel-9.iso, ironic-python-agent-rhel-10.kernel)
for all available CoreOS versions, allowing ICC to discover images by
stream. Also fixes missing initramfs symlinks in the fixed=true (PXE)
block — without these, a stream-specific kernel would be paired with the
default-stream initrd, causing a version mismatch that crashes immediately
on PXE boot.

image-customization-controller (openshift/image-customization-controller#175)
pkg/imagehandler/, pkg/imageprovider/rhcos.go: Adds OS stream
selection so ICC can serve different CoreOS base images based on the
coreos.openshift.io/stream label. Images are indexed by (stream,
architecture) and discovered from stream-prefixed filenames. Key changes:

  • streamArchSpecificURL() transforms the base rootfs URL into a stream-
    and arch-specific URL
  • BuildImage() passes the stream to ServeKernel() and uses the
    stream-specific rootfs URL in ExtraKernelParams
  • imageKey() includes the stream in the cache key so that changing a
    BMH's stream label invalidates the cached image

Summary by CodeRabbit

  • New Features

    • Stream-aware OS image and kernel selection/serving; images/kernels can be selected per stream and served with stream-specific URLs and rootfs handling.
    • Availability checks now consider stream-qualified images.
  • Bug Fixes

    • Improved base-image resolution with sensible stream+architecture fallbacks and clearer error when no matching base image exists.
  • Tests

    • Added unit tests for stream parsing, stream-aware image/kernel behavior, and stream+architecture URL handling.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3a12f4a4-a32c-4d4d-b340-9277b1f6f34b

📥 Commits

Reviewing files that changed from the base of the PR and between 1a77a73 and de2d0d6.

📒 Files selected for processing (8)
  • cmd/static-server/main.go
  • cmd/static-server/main_test.go
  • pkg/imagehandler/imagefile.go
  • pkg/imagehandler/imagefilesystem.go
  • pkg/imagehandler/imagehandler.go
  • pkg/imagehandler/imagehandler_test.go
  • pkg/imageprovider/rhcos.go
  • pkg/imageprovider/rhcos_test.go
✅ Files skipped from review due to trivial changes (2)
  • pkg/imagehandler/imagefile.go
  • pkg/imageprovider/rhcos_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • pkg/imagehandler/imagefilesystem.go
  • cmd/static-server/main_test.go
  • cmd/static-server/main.go
  • pkg/imageprovider/rhcos.go
  • pkg/imagehandler/imagehandler.go
  • pkg/imagehandler/imagehandler_test.go

Walkthrough

Adds a stream dimension to image selection and serving: new stream fields/keys, stream-aware filename parsing and lookup, ImageHandler API extended with stream, propagation of stream through provider and static-server call sites and tests, and fallback logic preserved.

Changes

Stream-aware image selection and serving

Layer / File(s) Summary
Data shape: image file
pkg/imagehandler/imagefile.go
Add stream string field to imageFile.
Index / keying / in-memory stores
pkg/imagehandler/imagehandler.go
Introduce streamArchKey and change internal isoFiles, initramfsFiles, kernelFiles to map[streamArchKey]...; extend osImage with stream.
Filename parsing and loading
pkg/imagehandler/imagehandler.go
Add matchStreamFilename and update loadOSImage to parse stream (and optional arch) and prefer stream+arch matches while preserving legacy arch-only fallback.
Core lookup and fallback logic
pkg/imagehandler/imagehandler.go, pkg/imagehandler/imagefilesystem.go
Implement getBaseImage(arch, stream, initramfs) and getKernel(arch, stream) with exact (stream,arch) lookup, fallback to default (empty) stream for arch and host-architecture fallbacks; imageFileSystem.Open forwards im.stream to kernel/base lookup.
Public API, caching and errors
pkg/imagehandler/imagehandler.go
Add stream parameter to ServeImage and ServeKernel signatures; include stream in cache keys and cached imageFile entries; ServeImage returns InvalidBaseImageError when no base exists for (arch,stream,initramfs); update HasImagesForArchitecture to consider streams.
Wiring: provider and static server
pkg/imageprovider/rhcos.go, cmd/static-server/main.go
Extract stream via streamFromImageData and include it in imageKey; BuildImage passes stream into ImageHandler.ServeImage and ServeKernel; static server call sites updated to pass stream (one site now passes an explicit empty stream argument).
URL helper behavior
pkg/imageprovider/rhcos.go
Replace archSpecificURL with streamArchSpecificURL which optionally appends -<stream> and conditionally _<arch> for non-host architectures while preserving query/fragment.
Tests and fakes
pkg/imagehandler/imagehandler_test.go, cmd/static-server/main_test.go, pkg/imageprovider/rhcos_test.go
Update test fixtures and fakes to accept/pass stream (fake ServeImage/ServeKernel signatures updated); refactor tests to use streamArchKey maps; add TestImagePatternWithStream and TestHasImagesForArchitectureWithStreams; add TestStreamArchSpecificURL and TestStreamFromImageData validating URL/stream logic.
sequenceDiagram
    participant Client
    participant ImageHandler
    participant ImageSelection
    participant ImageRetrieval

    Client->>ImageHandler: ServeImage(key, arch, stream, ...)
    ImageHandler->>ImageSelection: Lookup base by (stream, arch, initramfs)
    
    alt Exact (stream,arch) found
        ImageSelection->>ImageRetrieval: Return image file for (stream,arch)
        ImageRetrieval-->>ImageSelection: Image data/path
    else Fallback to default (empty) stream for arch
        ImageSelection->>ImageSelection: Retry (default_stream, arch)
        ImageSelection->>ImageRetrieval: Return image file
        ImageRetrieval-->>ImageSelection: Image data/path
    else Fallback to host architecture
        ImageSelection->>ImageSelection: Retry (stream/default, host_arch)
        ImageSelection->>ImageRetrieval: Return image file
        ImageRetrieval-->>ImageSelection: Image data/path
    else No base image
        ImageSelection-->>ImageHandler: nil / InvalidBaseImageError
    end

    ImageSelection-->>ImageHandler: imageFile (includes stream)
    ImageHandler-->>Client: served image URL/path
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

lgtm

Suggested reviewers

  • zaneb
🚥 Pre-merge checks | ✅ 11 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 clearly and accurately describes the main change: adding stream selection capability to support multiple CoreOS versions in the image-customization-controller.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed All test names in modified test files are stable and deterministic. No dynamic information found. Tests follow Go best practices with descriptive, static subtests.
Test Structure And Quality ✅ Passed Tests use standard Go testing, not Ginkgo. Tests show single responsibility, proper cleanup via t.TempDir(), meaningful assertion messages, and consistency with repo patterns.
Microshift Test Compatibility ✅ Passed No Ginkgo e2e tests added. PR contains only standard Go unit tests (testing.T) for image handling logic. Check not applicable.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No Ginkgo e2e tests are added in this PR. All new tests are standard Go unit tests. The custom check applies only to Ginkgo e2e tests and does not apply here.
Topology-Aware Scheduling Compatibility ✅ Passed Check not applicable. PR modifies image-serving logic only. No deployment, operator, or scheduling code.
Ote Binary Stdout Contract ✅ Passed All logging in process-level code routes to stderr via zap logger. No fmt.Print, log.Print, os.Stdout, or klog writes found. version.Print uses log.Info. No OTE stdout contract violations.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests are added in this PR. Only standard Go unit tests are present, which do not make IPv4 assumptions or require external connectivity. The check is not applicable.

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

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

@openshift-ci openshift-ci Bot requested review from dtantsur and zaneb April 14, 2026 17:29
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 14, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: honza

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

The pull request process is described here

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

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

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 14, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/imagehandler/imagehandler.go`:
- Around line 417-419: HasImagesForArchitecture currently probes only the
empty/default stream and misses stream-qualified assets; update it to be
stream-aware by iterating over the set of known streams on imageFileSystem (e.g.
the method/property that lists available streams such as
availableStreams()/streams) and for each stream call getBaseImage(arch, stream,
false) and getBaseImage(arch, stream, true) (same pair of checks ServeImage
would use) and return true if any call returns non-nil; if the struct has no
stream-listing API, fall back to checking the empty stream plus any stream
values ServeImage accepts so the detection matches ServeImage behavior (refer to
HasImagesForArchitecture, getBaseImage and ServeImage).

In `@pkg/imageprovider/rhcos.go`:
- Around line 157-170: The function streamArchSpecificURL treats an empty arch
as a distinct architecture and produces a bogus “_” suffix; update
streamArchSpecificURL to normalize empty architecture to mean host by setting
arch to env.HostArchitecture() when arch == "" (or otherwise capture hostArch :=
env.HostArchitecture() and treat empty arch as hostArch) and then only append
the "_<arch>" suffix when arch != hostArch; reference streamArchSpecificURL and
env.HostArchitecture() when making this change.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ec6b8ae2-9fdb-4350-a82b-541bb604193d

📥 Commits

Reviewing files that changed from the base of the PR and between a43d9c9 and 7319a87.

📒 Files selected for processing (7)
  • cmd/static-server/main.go
  • cmd/static-server/main_test.go
  • pkg/imagehandler/imagefile.go
  • pkg/imagehandler/imagefilesystem.go
  • pkg/imagehandler/imagehandler.go
  • pkg/imagehandler/imagehandler_test.go
  • pkg/imageprovider/rhcos.go

Comment thread pkg/imagehandler/imagehandler.go
Comment thread pkg/imageprovider/rhcos.go
@honza honza changed the title Add stream selection for multi-version support METAL-1730: Add stream selection for multi-version support Apr 14, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Apr 14, 2026
@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented Apr 14, 2026

@honza: This pull request references METAL-1730 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:

Enable the image-customization-controller to serve different CoreOS base images (e.g., RHCOS 9 vs RHCOS 10) based on a stream label on PreprovisioningImage resources. Images are now indexed by (stream, architecture) and discovered from stream-prefixed filenames like ironic-python-agent-rhel-9.iso. The coreos.openshift.io/stream label is read from PPI metadata and falls back to the default stream when absent.

How it works

The coreos.openshift.io/stream label on a BareMetalHost determines
which kernel, initrd, and rootfs files are used during PXE boot. For
example, a BMH labeled coreos.openshift.io/stream: rhel-10 will PXE
boot with ironic-python-agent-rhel-10.kernel,
ironic-python-agent-rhel-10.initramfs, and
ironic-python-agent-rhel-10.rootfs instead of the unqualified defaults.

The per-node coreos.live.rootfs_url kernel parameter set via ICC's
ExtraKernelParams overrides the global one from ironic-image because
dracut's getarg returns the last value for duplicate parameters.

Changes across repos

installer (openshift/installer#10502)
pkg/asset/machines/baremetal/: Sets the coreos.openshift.io/stream
label on all generated BareMetalHost objects (control-plane, worker,
arbiter) based on the install-config's OSImageStream setting, defaulting
to rhel-9. Configures hostSelector.matchLabels on
BareMetalMachineProviderSpec so MachineSets only claim BMHs with the
matching stream label.

cluster-baremetal-operator (openshift/cluster-baremetal-operator#590)
provisioning/image_customization.go: Passes three new environment
variables to the ICC container: DEPLOY_KERNEL (path to the kernel file),
IMAGE_SHARED_DIR (path to the shared images directory where
stream-prefixed files are discovered), and IRONIC_ROOTFS_URL (base URL
for the rootfs served by httpd). These enable ICC to discover multi-stream
images and construct per-node kernel/rootfs URLs.

machine-os-images (openshift/machine-os-images#83)
scripts/copy-metal: Creates stream-prefixed IPA symlinks (e.g.
ironic-python-agent-rhel-9.iso, ironic-python-agent-rhel-10.kernel)
for all available CoreOS versions, allowing ICC to discover images by
stream. Also fixes missing initramfs symlinks in the fixed=true (PXE)
block — without these, a stream-specific kernel would be paired with the
default-stream initrd, causing a version mismatch that crashes immediately
on PXE boot.

image-customization-controller (openshift/image-customization-controller#175)
pkg/imagehandler/, pkg/imageprovider/rhcos.go: Adds OS stream
selection so ICC can serve different CoreOS base images based on the
coreos.openshift.io/stream label. Images are indexed by (stream,
architecture) and discovered from stream-prefixed filenames. Key changes:

  • streamArchSpecificURL() transforms the base rootfs URL into a stream-
    and arch-specific URL
  • BuildImage() passes the stream to ServeKernel() and uses the
    stream-specific rootfs URL in ExtraKernelParams
  • imageKey() includes the stream in the cache key so that changing a
    BMH's stream label invalidates the cached image

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.

Comment thread pkg/imageprovider/rhcos.go
Comment thread pkg/imagehandler/imagehandler.go Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@pkg/imageprovider/rhcos_test.go`:
- Around line 16-22: The import block in pkg/imageprovider/rhcos_test.go is not
goimports-formatted; move the standard library import "testing" to its own group
(first), add a blank line, then list the external imports
(github.com/metal3-io/baremetal-operator/pkg/imageprovider,
github.com/openshift/image-customization-controller/pkg/env, metav1
"k8s.io/apimachinery/pkg/apis/meta/v1") in the external group (alphabetize if
desired) or simply run `goimports -w pkg/imageprovider/rhcos_test.go` to
automatically fix the grouping and ordering.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9db30bca-4784-499c-82ce-72a69c7bf078

📥 Commits

Reviewing files that changed from the base of the PR and between b6cba19 and 1a77a73.

📒 Files selected for processing (8)
  • cmd/static-server/main.go
  • cmd/static-server/main_test.go
  • pkg/imagehandler/imagefile.go
  • pkg/imagehandler/imagefilesystem.go
  • pkg/imagehandler/imagehandler.go
  • pkg/imagehandler/imagehandler_test.go
  • pkg/imageprovider/rhcos.go
  • pkg/imageprovider/rhcos_test.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/imagehandler/imagefile.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • cmd/static-server/main.go
  • pkg/imagehandler/imagefilesystem.go
  • cmd/static-server/main_test.go
  • pkg/imageprovider/rhcos.go
  • pkg/imagehandler/imagehandler.go
  • pkg/imagehandler/imagehandler_test.go

Comment thread pkg/imageprovider/rhcos_test.go
Enable the image-customization-controller to serve different CoreOS
base images (e.g., RHCOS 9 vs RHCOS 10) based on a stream label
on PreprovisioningImage resources. Images are now indexed by
(stream, architecture) and discovered from stream-prefixed filenames
like ironic-python-agent-rhel-9.iso. The coreos.openshift.io/stream
label is read from PPI metadata and falls back to the default stream
when absent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@elfosardo
Copy link
Copy Markdown
Contributor

/lgtm

@elfosardo
Copy link
Copy Markdown
Contributor

/retest

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label May 13, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 13, 2026

@honza: all tests passed!

Full PR test history. Your PR dashboard.

Details

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

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants