Skip to content

Extend Node Image Info to handle streams#749

Merged
sdodson merged 5 commits intoopenshift:mainfrom
sdodson:rhcos-stream-changelogs
Apr 8, 2026
Merged

Extend Node Image Info to handle streams#749
sdodson merged 5 commits intoopenshift:mainfrom
sdodson:rhcos-stream-changelogs

Conversation

@sdodson
Copy link
Copy Markdown
Member

@sdodson sdodson commented Mar 31, 2026

I have tested this across the following transitions and found results as expected

  • 4.12.86 to 4.12.87 (el8.6 pre native ostree)
  • 4.18.35 to 4.18.37 (el9.4 only image w/ native ostree)
  • 4.20.18 to 4.21.9 (el9_6 to el9.6 + el10.2, displays diff for el9.6 and simple listing for 10.2)
  • 4.21.8 to 4.21.9 (el9.6 and el10.2 to later versions, shows two separate diffs)

I've tested this with the sample changelog generator in hack/changelog-preview but not in a full RC deployment.

Sample output at https://gist.github.com/sdodson/91e0703c3b3a5b3b59cce2e83713f370

Co-authored-by: Cursor noreply@cursor.com
Assisted-by: Cursor AI (Composer)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added standalone changelog preview tool to generate changelogs without requiring cluster access.
    • Enhanced changelog generation to support multiple machine OS streams with separate package information.
    • Added RHEL 10 CoreOS support to changelog transformations.
    • Improved node image section with multi-stream capability.
  • Bug Fixes

    • Fixed changelog JSON response handling to prevent unintended downstream logic execution.

sdodson and others added 4 commits March 30, 2026 23:02
…overy

Add ListMachineOSStreams from release payload JSON (coreos + extensions pairing,
io.openshift.build.version-display-names for titles). Extend ReleaseInfo with
ImageReferenceForComponent, RpmListForStream, RpmDiffForStream, and groupcache
keys. Add ReleaseTagIsDualRHCOS semver helper for 4.21+.

Co-authored-by: Cursor <noreply@cursor.com>
Assisted-by: Cursor AI (Composer)
Transform RHEL 9/10 and CentOS CoreOS lines; add NodeImageSectionMarkdown using
ListMachineOSStreams for generic per-stream RPM lists and diffs.
Render Node Image Info when streams exist even without #node-image-info anchor.

Co-authored-by: Cursor <noreply@cursor.com>
Assisted-by: Cursor AI (Composer)
… exist

Match changelog-preview behavior: load node section when the changelog has
#node-image-info or ListMachineOSStreams returns streams for the target release.

Co-authored-by: Cursor <noreply@cursor.com>
Assisted-by: Cursor AI (Composer)
Local tool to run ChangeLog + RHCOS transforms without a cluster (requires oc).
Ignore generated sample-changelog.md.

Co-authored-by: Cursor <noreply@cursor.com>
Assisted-by: Cursor AI (Composer)
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 31, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Mar 31, 2026

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 492f1f57-c046-4ec7-a3ad-e556e7d2efe8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@sdodson
Copy link
Copy Markdown
Member Author

sdodson commented Mar 31, 2026

/retest-required

@sdodson
Copy link
Copy Markdown
Member Author

sdodson commented Apr 6, 2026

@stbenjam @jlebon @bradmwilliams PTAL

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
pkg/release-controller/machine_os_tags_test.go (1)

71-78: Consider testing additional known tag mappings.

The test covers the DisplayName present case and the empty DisplayName fallback, but doesn't test the hardcoded fallbacks for known tags like rhel-coreos-10 or stream-coreos defined in MachineOSTitle.

💡 Suggested additional assertions
 func TestMachineOSTitle(t *testing.T) {
 	if got := MachineOSTitle(MachineOSStreamInfo{Tag: "rhel-coreos", DisplayName: "Red Hat Enterprise Linux CoreOS"}); got != "Red Hat Enterprise Linux CoreOS (`rhel-coreos`)" {
 		t.Errorf("got %q", got)
 	}
 	if got := MachineOSTitle(MachineOSStreamInfo{Tag: "custom-stream", DisplayName: ""}); got != "Machine OS (`custom-stream`)" {
 		t.Errorf("got %q", got)
 	}
+	// Test hardcoded fallbacks for known tags without DisplayName
+	if got := MachineOSTitle(MachineOSStreamInfo{Tag: "rhel-coreos", DisplayName: ""}); got != "Red Hat Enterprise Linux CoreOS (`rhel-coreos`)" {
+		t.Errorf("rhel-coreos fallback: got %q", got)
+	}
+	if got := MachineOSTitle(MachineOSStreamInfo{Tag: "rhel-coreos-10", DisplayName: ""}); got != "Red Hat Enterprise Linux CoreOS 10 (`rhel-coreos-10`)" {
+		t.Errorf("rhel-coreos-10 fallback: got %q", got)
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/release-controller/machine_os_tags_test.go` around lines 71 - 78, Add
assertions to TestMachineOSTitle to cover the hardcoded fallback mappings inside
MachineOSTitle: call MachineOSTitle with MachineOSStreamInfo having Tag values
like "rhel-coreos-10" and "stream-coreos" (and empty DisplayName) and assert the
returned string matches the expected human-readable fallbacks (e.g., the
specific names MachineOSTitle maps those tags to, including the backticked tag
in parentheses). Locate the test function TestMachineOSTitle and extend it with
these additional checks using the same pattern as the existing assertions to
ensure the known-tag branches are exercised.
hack/changelog-preview/main.go (1)

75-77: Consider extracting the "Node Image Info" header as a constant or shared function.

The header "## Node Image Info" is hardcoded here and also rendered in cmd/release-controller-api/http_changelog.go (line 197). To maintain consistency, consider extracting this to a shared constant or having NodeImageSectionMarkdown include the header.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hack/changelog-preview/main.go` around lines 75 - 77, The hardcoded header
"## Node Image Info" is duplicated; update the code so the header is defined
once and reused: either add a shared constant (e.g., NodeImageInfoHeader) and
replace the literal in hack/changelog-preview/main.go (where nodeMD is appended
to out) and in cmd/release-controller-api/http_changelog.go, or move the header
into the NodeImageSectionMarkdown implementation so callers just append nodeMD
without adding the header; ensure all references (nodeMD,
NodeImageSectionMarkdown) use the single source of truth for the header.
pkg/rhcos/rhcos_test.go (1)

234-246: Consider making the assertion more specific.

The test checks for at least 2 occurrences of "coreos-base-alert", which validates that dual CoreOS rendering works. However, checking for exactly 2 would be more precise if dual RHCOS should produce exactly two infoboxes.

💡 Suggested improvement
-	if strings.Count(out, "coreos-base-alert") < 2 {
-		t.Fatalf("expected two CoreOS base layer infoboxes, got:\n%s", out)
+	count := strings.Count(out, "coreos-base-alert")
+	if count != 2 {
+		t.Fatalf("expected exactly two CoreOS base layer infoboxes, got %d:\n%s", count, out)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/rhcos/rhcos_test.go` around lines 234 - 246, The test
TestTransformMarkDownOutputDualRHCOSLines currently asserts that
strings.Count(out, "coreos-base-alert") < 2 to ensure at least two infoboxes;
change this to assert exactly two occurrences (e.g., if strings.Count(out,
"coreos-base-alert") != 2) and update the failure message accordingly so the
test fails when there are more or fewer than two coreos-base-alert infoboxes,
ensuring precise verification of dual RHCOS rendering.
pkg/release-controller/semver_dual_rhcos_test.go (1)

5-24: Consider adding edge cases for more complete coverage.

The test covers the main scenarios well but could benefit from additional edge cases:

  • Major version != 4 (e.g., "5.21.0" should return false)
  • Empty string input
  • Partial/incomplete versions that SemverParseTolerant might handle (e.g., "4.21")
💡 Suggested additional test cases
 	tests := []struct {
 		tag  string
 		want bool
 	}{
 		{"4.21.0-ec.1", true},
 		{"4.21.0", true},
 		{"4.22.1", true},
 		{"4.20.0", false},
 		{"4.20.0-ec.0", false},
 		{"not-a-version", false},
+		{"", false},
+		{"5.21.0", false},       // Major != 4
+		{"3.21.0", false},       // Major != 4
+		{"4.21", true},          // Two-part version (if SemverParseTolerant handles it)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/release-controller/semver_dual_rhcos_test.go` around lines 5 - 24, Add
tests for missing edge cases to TestReleaseTagIsDualRHCOS: include a non-4 major
version like "5.21.0" expecting false, an empty-string input "" expecting false,
and a partial version "4.21" (and optionally "4" or other incomplete forms) to
assert whatever SemverParseTolerant should produce (e.g., "4.21" -> true if
parsed as 4.21); use the same t.Run/expect pattern and the ReleaseTagIsDualRHCOS
helper so the new cases integrate with existing table-driven tests.
pkg/release-controller/machine_os_tags.go (1)

124-147: Consider adding explicit priority for rhel-coreos-10.

The sort priority map includes rhel-coreos (0) and stream-coreos (1), but rhel-coreos-10 will fall through to lexicographic sorting. This works correctly today ("rhel-coreos" < "rhel-coreos-10" lexicographically), but if a new variant like rhel-coreos-9 is introduced, the ordering might become unexpected.

💡 Explicit priority for all known tags
 func machineOSTagLess(a, b string) bool {
 	prio := map[string]int{
-		"rhel-coreos":   0,
-		"stream-coreos": 1,
+		"rhel-coreos":    0,
+		"rhel-coreos-10": 1,
+		"stream-coreos":  2,
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/release-controller/machine_os_tags.go` around lines 124 - 147, The
current machine OSTag ordering in machineOSTagLess relies on exact keys in the
prio map so variants like "rhel-coreos-10" fall back to lexicographic sort;
update machineOSTagLess (used by sortMachineOSTags) to either add explicit
priority entries for known variants (e.g., "rhel-coreos-10": 0 and any other
rhel-coreos-X variants) or implement a prefix-based check (treat strings with
prefix "rhel-coreos" as the same priority as "rhel-coreos") so all rhel-coreos
variants sort consistently ahead of "stream-coreos". Ensure the chosen change
updates the prio lookup logic and retains the existing fallback to lexicographic
comparison for unknown tags.
cmd/release-controller-api/http_changelog.go (1)

183-195: Consider consolidating duplicate anchor+streams detection logic.

The logic to determine whether to show node image info (check anchor, then check streams) is duplicated between getChangeLog (lines 95-103) and renderChangeLog (lines 183-192). While they serve slightly different purposes (one for async fetch initiation, one for render decision), the duplication could lead to drift.

Note: The silent error handling (if err == nil) on lines 186 and 188 is acceptable here since this is an optional UI enhancement—failing to show node info shouldn't break the page.

💡 Potential consolidation

Consider extracting a helper function:

func (c *Controller) shouldShowNodeImageInfo(out string, toPull string) bool {
    if strings.Contains(out, "#node-image-info") {
        return true
    }
    toImage, err := releasecontroller.GetImageInfo(c.releaseInfo, c.architecture, toPull)
    if err != nil {
        return false
    }
    streams, err := c.releaseInfo.ListMachineOSStreams(toImage.GenerateDigestPullSpec())
    return err == nil && len(streams) > 0
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/release-controller-api/http_changelog.go` around lines 183 - 195, The
duplicate logic that decides whether to display node image info in getChangeLog
and renderChangeLog should be extracted into a single helper on Controller
(e.g., shouldShowNodeImageInfo) to avoid drift: move the combined check
(strings.Contains(render/out, "#node-image-info") then fallback to calling
releasecontroller.GetImageInfo and c.releaseInfo.ListMachineOSStreams on the
generated digest pull spec) into that helper, update both getChangeLog and
renderChangeLog to call Controller.shouldShowNodeImageInfo(out, toPull) (or
appropriate parameter names), and preserve the current silent error handling
(treat errors as false) so behavior remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hack/changelog-preview/main.go`:
- Around line 29-31: The help text for the flag variables defined in main uses
an incorrect format ("digest or tag@repo"); update the flag descriptions created
via flag.String for the variables from and to so they reflect the standard image
pull spec forms (e.g., "repository:tag or repository@digest (sha256:...)" or
similar), making sure the strings passed to flag.String for from and to are
corrected to the proper wording.

In `@pkg/release-controller/release_info.go`:
- Around line 128-165: Add the same NUL-byte validation used in the existing
handlers to the "rpmliststream", "imagefor", and "machineosstreams" branches:
detect embedded NULs in the relevant parts before using them (as done in the
"rpmdiffstream"/"bugs"/"rpmdiff"/"changelog" handlers) and return an error like
"invalid <key> key" when a NUL is found; update the checks around
info.RpmListForStream, info.ImageReferenceForComponent, and
info.ListMachineOSStreams to perform this validation on parts[1..n] prior to
calling those functions.

---

Nitpick comments:
In `@cmd/release-controller-api/http_changelog.go`:
- Around line 183-195: The duplicate logic that decides whether to display node
image info in getChangeLog and renderChangeLog should be extracted into a single
helper on Controller (e.g., shouldShowNodeImageInfo) to avoid drift: move the
combined check (strings.Contains(render/out, "#node-image-info") then fallback
to calling releasecontroller.GetImageInfo and c.releaseInfo.ListMachineOSStreams
on the generated digest pull spec) into that helper, update both getChangeLog
and renderChangeLog to call Controller.shouldShowNodeImageInfo(out, toPull) (or
appropriate parameter names), and preserve the current silent error handling
(treat errors as false) so behavior remains unchanged.

In `@hack/changelog-preview/main.go`:
- Around line 75-77: The hardcoded header "## Node Image Info" is duplicated;
update the code so the header is defined once and reused: either add a shared
constant (e.g., NodeImageInfoHeader) and replace the literal in
hack/changelog-preview/main.go (where nodeMD is appended to out) and in
cmd/release-controller-api/http_changelog.go, or move the header into the
NodeImageSectionMarkdown implementation so callers just append nodeMD without
adding the header; ensure all references (nodeMD, NodeImageSectionMarkdown) use
the single source of truth for the header.

In `@pkg/release-controller/machine_os_tags_test.go`:
- Around line 71-78: Add assertions to TestMachineOSTitle to cover the hardcoded
fallback mappings inside MachineOSTitle: call MachineOSTitle with
MachineOSStreamInfo having Tag values like "rhel-coreos-10" and "stream-coreos"
(and empty DisplayName) and assert the returned string matches the expected
human-readable fallbacks (e.g., the specific names MachineOSTitle maps those
tags to, including the backticked tag in parentheses). Locate the test function
TestMachineOSTitle and extend it with these additional checks using the same
pattern as the existing assertions to ensure the known-tag branches are
exercised.

In `@pkg/release-controller/machine_os_tags.go`:
- Around line 124-147: The current machine OSTag ordering in machineOSTagLess
relies on exact keys in the prio map so variants like "rhel-coreos-10" fall back
to lexicographic sort; update machineOSTagLess (used by sortMachineOSTags) to
either add explicit priority entries for known variants (e.g., "rhel-coreos-10":
0 and any other rhel-coreos-X variants) or implement a prefix-based check (treat
strings with prefix "rhel-coreos" as the same priority as "rhel-coreos") so all
rhel-coreos variants sort consistently ahead of "stream-coreos". Ensure the
chosen change updates the prio lookup logic and retains the existing fallback to
lexicographic comparison for unknown tags.

In `@pkg/release-controller/semver_dual_rhcos_test.go`:
- Around line 5-24: Add tests for missing edge cases to
TestReleaseTagIsDualRHCOS: include a non-4 major version like "5.21.0" expecting
false, an empty-string input "" expecting false, and a partial version "4.21"
(and optionally "4" or other incomplete forms) to assert whatever
SemverParseTolerant should produce (e.g., "4.21" -> true if parsed as 4.21); use
the same t.Run/expect pattern and the ReleaseTagIsDualRHCOS helper so the new
cases integrate with existing table-driven tests.

In `@pkg/rhcos/rhcos_test.go`:
- Around line 234-246: The test TestTransformMarkDownOutputDualRHCOSLines
currently asserts that strings.Count(out, "coreos-base-alert") < 2 to ensure at
least two infoboxes; change this to assert exactly two occurrences (e.g., if
strings.Count(out, "coreos-base-alert") != 2) and update the failure message
accordingly so the test fails when there are more or fewer than two
coreos-base-alert infoboxes, ensuring precise verification of dual RHCOS
rendering.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 78d0ae95-391b-4882-9a65-80b2bef0c4b8

📥 Commits

Reviewing files that changed from the base of the PR and between 6dda498 and 8033282.

📒 Files selected for processing (11)
  • .gitignore
  • cmd/release-controller-api/http_changelog.go
  • hack/changelog-preview/main.go
  • pkg/release-controller/machine_os_tags.go
  • pkg/release-controller/machine_os_tags_test.go
  • pkg/release-controller/release_info.go
  • pkg/release-controller/semver.go
  • pkg/release-controller/semver_dual_rhcos_test.go
  • pkg/rhcos/node_image.go
  • pkg/rhcos/rhcos.go
  • pkg/rhcos/rhcos_test.go

Comment on lines +29 to +31
func main() {
from := flag.String("from", "", "from release image pull spec (digest or tag@repo)")
to := flag.String("to", "", "to release image pull spec")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typo in help text.

Line 30 says "digest or tag@repo" but the standard format is repository:tag or repository@digest (sha256:...). The help text should reflect this.

📝 Suggested fix
-	from := flag.String("from", "", "from release image pull spec (digest or tag@repo)")
+	from := flag.String("from", "", "from release image pull spec (repo:tag or repo@sha256:...)")
📝 Committable suggestion

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

Suggested change
func main() {
from := flag.String("from", "", "from release image pull spec (digest or tag@repo)")
to := flag.String("to", "", "to release image pull spec")
func main() {
from := flag.String("from", "", "from release image pull spec (repo:tag or repo@sha256:...)")
to := flag.String("to", "", "to release image pull spec")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hack/changelog-preview/main.go` around lines 29 - 31, The help text for the
flag variables defined in main uses an incorrect format ("digest or tag@repo");
update the flag descriptions created via flag.String for the variables from and
to so they reflect the standard image pull spec forms (e.g., "repository:tag or
repository@digest (sha256:...)" or similar), making sure the strings passed to
flag.String for from and to are corrected to the proper wording.

Comment thread pkg/release-controller/release_info.go
@bradmwilliams
Copy link
Copy Markdown
Collaborator

/label tide/merge-method-squash

@bradmwilliams
Copy link
Copy Markdown
Collaborator

/approve
Local testing, of the API server, appears to do what's been described above.

@openshift-ci openshift-ci Bot added the tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges. label Apr 6, 2026
@bradmwilliams
Copy link
Copy Markdown
Collaborator

/hold
@stbenjam this will definitely have an impact on #750

@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 Apr 6, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 6, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bradmwilliams, sdodson

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 6, 2026
@stbenjam
Copy link
Copy Markdown
Member

stbenjam commented Apr 6, 2026

this will definitely have an impact on #750

No problem from me, I'll update #750 to handle the dual-stream case after this lands

/hold cancel

@openshift-ci openshift-ci Bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Apr 6, 2026
@sdodson sdodson marked this pull request as ready for review April 6, 2026 23:23
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 6, 2026
@openshift-ci openshift-ci Bot requested review from AlexNPavel and hoxhaeris April 6, 2026 23:23
to check for null bytes in all inputs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@sdodson
Copy link
Copy Markdown
Member Author

sdodson commented Apr 6, 2026

Sorry, I hadn't noticed that the PR was still in draft state, should be good to go now.

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 6, 2026

@sdodson: 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.

@sdodson sdodson merged commit ac95940 into openshift:main Apr 8, 2026
7 of 8 checks passed
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. tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants