Skip to content

WIP: CLID-290: feat: Add sparse manifest platform filtering for multi-arch images#1435

Open
aguidirh wants to merge 1 commit into
openshift:mainfrom
aguidirh:CLID-290
Open

WIP: CLID-290: feat: Add sparse manifest platform filtering for multi-arch images#1435
aguidirh wants to merge 1 commit into
openshift:mainfrom
aguidirh:CLID-290

Conversation

@aguidirh

@aguidirh aguidirh commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Description

This PR implements sparse manifest platform filtering for multi-architecture images in oc-mirror. Users can now specify OS/Architecture pairs to filter which platforms are mirrored, significantly reducing disk usage and bandwidth requirements.

The implementation brings the platform filtering capability from containers/image (containers/container-libs PR #656) to oc-mirror. It uses sparse manifests where the manifest list references all platforms but only the specified platforms have their actual layer blobs downloaded.

Platform filtering works across all image types:

  • Additional images
  • Operator catalogs
  • Helm charts
  • Release images (with backward compatibility for deprecated architectures field)

Github / Jira issue: CLID-290

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update on openshift docs

How Has This Been Tested?

Still in Progress.

Expected Outcome

Platform filtering reduces disk usage by 60-75% when mirroring only specific platforms instead of all architectures. Sparse manifests are created where manifest lists reference all platforms but only selected platforms have layer blobs downloaded.

Summary by CodeRabbit

  • New Features
    • Added platform selection support in configuration for operators, Helm charts, and additional images, enabling multi-architecture specification via OS/architecture pairs.
    • Platform filters are now propagated throughout the mirroring pipeline, ensuring per-image platform requirements are respected during mirror operations.

@openshift-ci openshift-ci Bot requested review from adolfo-ab and r4f4 June 8, 2026 15:16
@openshift-ci

openshift-ci Bot commented Jun 8, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: aguidirh

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 Jun 8, 2026
@aguidirh aguidirh changed the title feat: Add sparse manifest platform filtering for multi-arch images WIP: CLID-290: feat: Add sparse manifest platform filtering for multi-arch images Jun 8, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jun 8, 2026
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 8, 2026
@openshift-ci-robot

openshift-ci-robot commented Jun 8, 2026

Copy link
Copy Markdown

@aguidirh: This pull request references CLID-290 which is a valid jira issue.

Details

In response to this:

Description

This PR implements sparse manifest platform filtering for multi-architecture images in oc-mirror. Users can now specify OS/Architecture pairs to filter which platforms are mirrored, significantly reducing disk usage and bandwidth requirements.

The implementation brings the platform filtering capability from containers/image (containers/container-libs PR #656) to oc-mirror. It uses sparse manifests where the manifest list references all platforms but only the specified platforms have their actual layer blobs downloaded.

Platform filtering works across all image types:

  • Additional images
  • Operator catalogs
  • Helm charts
  • Release images (with backward compatibility for deprecated architectures field)

Github / Jira issue: CLID-290

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update on openshift docs

How Has This Been Tested?

Still in Progress.

Expected Outcome

Platform filtering reduces disk usage by 60-75% when mirroring only specific platforms instead of all architectures. Sparse manifests are created where manifest lists reference all platforms but only selected platforms have layer blobs downloaded.

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 8, 2026

Copy link
Copy Markdown

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

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: f1eb11d4-7abf-49ba-a236-cda5e8836634

📥 Commits

Reviewing files that changed from the base of the PR and between 9074ce3 and 55faa30.

📒 Files selected for processing (15)
  • internal/pkg/additional/local_stored_collector.go
  • internal/pkg/api/v2alpha1/type_config.go
  • internal/pkg/api/v2alpha1/type_internal.go
  • internal/pkg/api/v2alpha1/type_platform.go
  • internal/pkg/batch/concurrent_chan_worker.go
  • internal/pkg/helm/local_stored_collector.go
  • internal/pkg/mirror/mirror.go
  • internal/pkg/mirror/mirror_test.go
  • internal/pkg/mirror/options.go
  • internal/pkg/operator/catalog_handler.go
  • internal/pkg/operator/catalog_handler_test.go
  • internal/pkg/operator/filtered_collector.go
  • internal/pkg/operator/filtered_collector_test.go
  • internal/pkg/operator/interface.go
  • internal/pkg/release/local_stored_collector.go
✅ Files skipped from review due to trivial changes (1)
  • internal/pkg/mirror/options.go
🚧 Files skipped from review as they are similar to previous changes (11)
  • internal/pkg/api/v2alpha1/type_platform.go
  • internal/pkg/operator/catalog_handler_test.go
  • internal/pkg/operator/filtered_collector.go
  • internal/pkg/mirror/mirror_test.go
  • internal/pkg/api/v2alpha1/type_internal.go
  • internal/pkg/operator/catalog_handler.go
  • internal/pkg/helm/local_stored_collector.go
  • internal/pkg/operator/filtered_collector_test.go
  • internal/pkg/api/v2alpha1/type_config.go
  • internal/pkg/release/local_stored_collector.go
  • internal/pkg/mirror/mirror.go

Walkthrough

This PR introduces per-image platform filtering across the oc-mirror pipeline. A new InstancePlatformFilter type and ConvertPlatformsToStringSlice utility are added. Platforms fields are added to config types (Platform, Operator, Chart, AdditionalImage) and internal types (RelatedImage, CopyImageSchema). The mirror engine's parseMultiArch and new determinePlatformSelection are updated to support comma-separated OS/ARCH platform lists. All collector paths (release, Helm, operator, additional images) and the batch worker are updated to propagate platform filters from configuration through to copy operations.

Changes

Per-image platform filtering across oc-mirror

Layer / File(s) Summary
Platform type, config, and internal struct contracts
internal/pkg/api/v2alpha1/type_platform.go, internal/pkg/api/v2alpha1/type_config.go, internal/pkg/api/v2alpha1/type_internal.go, internal/pkg/mirror/options.go
InstancePlatformFilter struct and ConvertPlatformsToStringSlice are added. Platforms []InstancePlatformFilter is added to Platform (with deprecated architectures documented), Operator, Chart, and AdditionalImage config types, along with Platform.DeepCopy() update. Platforms *[]string is added to RelatedImage and CopyImageSchema. CopyOptions.InstancePlatforms []string is added.
Mirror engine platform selection
internal/pkg/mirror/mirror.go, internal/pkg/mirror/mirror_test.go
determinePlatformSelection is added to prefer opts.InstancePlatforms over legacy --all/--multi-arch flags. parseMultiArch is updated to return both ImageListSelection and []InstancePlatformFilter, supporting comma-separated OS/ARCH input. copy() is wired to use the computed platform filters. Tests are updated for new error messages and comma-separated parse cases.
Operator collector platform propagation
internal/pkg/operator/interface.go, internal/pkg/operator/catalog_handler.go, internal/pkg/operator/filtered_collector.go, internal/pkg/operator/catalog_handler_test.go, internal/pkg/operator/filtered_collector_test.go
catalogHandlerInterface.getRelatedImagesFromCatalog gains a platforms *[]string parameter. CatalogHandler.getRelatedImagesFromCatalog and handleRelatedImages accept and assign platforms to each constructed RelatedImage. collectOperator converts op.Platforms and passes it through. Tests and mock are updated accordingly.
Release collector platform propagation
internal/pkg/release/local_stored_collector.go
getPlatformFilters() is added to derive platform filters from Platform.Platforms or deprecated Architectures. Mirror-based collection assigns filters to all RelatedImage entries. prepareM2DCopyBatch, prepareD2MCopyBatch, and all three handleGraphImage return paths propagate Platforms to CopyImageSchema.
Helm collector platform propagation
internal/pkg/helm/local_stored_collector.go
getImages gains a platforms parameter; M2D, D2M, and local chart collector call sites convert chart.Platforms and pass it. getImages assigns platforms to each discovered RelatedImage. prepareM2DCopyBatch and prepareD2MCopyBatch propagate RelatedImage.Platforms to CopyImageSchema.
Additional images collector and batch worker
internal/pkg/additional/local_stored_collector.go, internal/pkg/batch/concurrent_chan_worker.go
AdditionalImagesCollector converts img.Platforms and sets it on the appended CopyImageSchema. ChannelConcurrentBatch.Worker sets options.InstancePlatforms from img.Platforms before calling Mirror.Run.

Sequence Diagram(s)

sequenceDiagram
  participant Collector as Collector (Release/Helm/Operator/Additional)
  participant RelatedImage as RelatedImage
  participant BatchWorker as ChannelConcurrentBatch.Worker
  participant MirrorEngine as mirror.copy()
  participant determinePlatformSelection as determinePlatformSelection
  participant parseMultiArch as parseMultiArch

  Collector->>RelatedImage: sets Platforms *[]string from ConvertPlatformsToStringSlice
  Collector->>BatchWorker: enqueues CopyImageSchema with Platforms
  BatchWorker->>MirrorEngine: sets options.InstancePlatforms from img.Platforms
  MirrorEngine->>determinePlatformSelection: opts.InstancePlatforms or MultiArch/All
  determinePlatformSelection->>parseMultiArch: comma-separated OS/ARCH or legacy mode
  parseMultiArch-->>determinePlatformSelection: ImageListSelection + []InstancePlatformFilter
  determinePlatformSelection-->>MirrorEngine: imageListSelection + instancePlatforms
  MirrorEngine->>MirrorEngine: copy.Options{InstancePlatforms: instancePlatforms}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 13 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ⚠️ Warning PR tests use standard Go testing (not Ginkgo), making BeforeEach/AfterEach inapplicable. However, bare assertions like assert.NoError(t, err) without meaningful messages were found in catalog_han... Add context messages to all bare assertions: assert.NoError(t, err, "should handle related images") instead of assert.NoError(t, err), following Go testing best practices and existing message patterns in mirror_test.go.
✅ Passed checks (13 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the primary feature: adding sparse manifest platform filtering for multi-arch images. It is specific, concise, and accurately reflects the main objective of 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 All test names in the modified test files (mirror_test.go, catalog_handler_test.go, filtered_collector_test.go) use static, hardcoded strings with no dynamic values (timestamps, UUIDs, pod names, e...
Microshift Test Compatibility ✅ Passed No new Ginkgo e2e tests were added in this PR. All test changes are limited to internal unit tests using Go's standard testing package, not Ginkgo e2e tests, so the MicroShift Test Compatibility ch...
Single Node Openshift (Sno) Test Compatibility ✅ Passed No Ginkgo e2e tests added in this PR. This PR implements platform filtering for oc-mirror using only standard Go unit tests, making the SNO compatibility check not applicable.
Topology-Aware Scheduling Compatibility ✅ Passed This PR modifies only the oc-mirror CLI tool's image platform filtering logic through Go source files. No Kubernetes deployment manifests, controllers, or scheduling constraints are introduced or m...
Ote Binary Stdout Contract ✅ Passed No process-level stdout writes or logging configuration violations found. All logging in test files is inside test functions, not in process-level code (main, init, BeforeSuite, AfterSuite, etc.)....
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PR adds no new Ginkgo e2e tests. Only internal packages (14 files) and unit tests (3 files) modified; no changes to tests/integration or e2e test directories.
No-Weak-Crypto ✅ Passed PR implements platform filtering for multi-arch images using simple string operations and data structures; no MD5/SHA1/weak ciphers/custom crypto implementations or insecure comparisons are introdu...
Container-Privileges ✅ Passed PR adds platform filtering feature to Go CLI tool (oc-mirror); no Kubernetes manifests with privileged container configs, hostPID/hostNetwork/hostIPC, SYS_ADMIN, or allowPrivilegeEscalation were in...
No-Sensitive-Data-In-Logs ✅ Passed PR introduces no new logging of sensitive data. New code handles platform filtering (OS/architecture pairs) without logging values, and error messages only show format examples for user guidance.

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (1)
internal/pkg/api/v2alpha1/type_config.go (1)

116-130: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Platform.DeepCopy() drops scalar fields and returns an incomplete copy.

Line 117 only seeds Graph; Release and KubeVirtContainer are lost in the copied struct. That can silently alter mirroring behavior when callers depend on DeepCopy().

Proposed fix
 func (p Platform) DeepCopy() Platform {
 	platformCopy := Platform{
-		Graph: p.Graph,
+		Graph:             p.Graph,
+		Release:           p.Release,
+		KubeVirtContainer: p.KubeVirtContainer,
 	}
🤖 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 `@internal/pkg/api/v2alpha1/type_config.go` around lines 116 - 130,
Platform.DeepCopy() currently initializes platformCopy with only Graph, causing
scalar fields like Release and KubeVirtContainer to be lost; update
Platform.DeepCopy to copy all scalar and value fields from the receiver (e.g.,
set platformCopy.Release = p.Release and platformCopy.KubeVirtContainer =
p.KubeVirtContainer) in addition to the existing deep copies of slices
(Channels, Architectures, Platforms) so the returned copy is complete; locate
the DeepCopy method on type Platform and add assignments for any other
scalar/value fields to mirror the source struct.
🧹 Nitpick comments (2)
internal/pkg/operator/filtered_collector_test.go (1)

1052-1061: ⚡ Quick win

Make the mock validate the forwarded platforms argument.

The new parameter is currently ignored, so these tests won’t detect broken propagation from operator config to catalog handler calls.

🤖 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 `@internal/pkg/operator/filtered_collector_test.go` around lines 1052 - 1061,
The mock getRelatedImagesFromCatalog in MockHandler ignores the new platforms
parameter; update it to validate the forwarded platforms pointer (the platforms
*[]string argument) before returning results — e.g., check platforms is not nil
and that *platforms matches the expected slice (or contains required entries)
and return an error (or fail) when it does not so tests detect propagation
failures; keep the existing relatedImages behavior otherwise and leave
copyImageSchemaMap untouched.
internal/pkg/operator/catalog_handler_test.go (1)

43-43: ⚡ Quick win

Add one non-nil platforms case to validate propagation in returned related images.

Current update only checks the nil path, so the new behavior can regress without test failure.

🤖 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 `@internal/pkg/operator/catalog_handler_test.go` at line 43, Add a test entry
in catalog_handler_test.go that calls handler.getRelatedImagesFromCatalog with a
non-nil platforms argument (e.g., a slice like []string{"linux/amd64"}) in
addition to the existing nil-case; call the same function used in the diff
(getRelatedImagesFromCatalog) with that non-nil platforms and
copyImageSchemaMap, assert err == nil, and verify that each returned related
image in res has its Platforms (or equivalent field) set to the same non-nil
slice (or contains the provided platforms), thereby validating propagation of
the platforms parameter through the function.
🤖 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 `@go.mod`:
- Line 8: Update the pinned module version for
github.com/distribution/distribution/v3 in go.mod from v3.0.0 to a patched
release (>= v3.1.1) to address the listed GHSA advisories; after editing the
version string, run the appropriate Go module commands (e.g., go get
github.com/distribution/distribution/v3@v3.1.1 and go mod tidy) to update go.sum
and ensure build/test pass, and verify no other transitive constraints (e.g.,
from github.com/containerd/containerd or github.com/moby/spdystream) prevent the
upgrade.
- Line 138: The project currently pins github.com/moby/spdystream v0.5.0; update
the dependency to v0.5.1+ by bumping the parent module that pulls it (or
directly requesting the newer version) so the vulnerable transitive dependency
is resolved, then run go mod tidy to refresh go.sum; locate references to
github.com/moby/spdystream in go.mod (and the parent module that requires it)
and run a go get or otherwise update that parent to force
github.com/moby/spdystream@v0.5.1 (or later), followed by go mod tidy to update
go.sum.
- Line 63: Update the indirect dependency github.com/containerd/containerd from
v1.7.29 to at least v1.7.32 in go.mod to address GHSA-fqw6-gf59-qr4w; edit the
require directive for "github.com/containerd/containerd" to "v1.7.32" or a newer
compatible v1.7.x+ version, run "go get
github.com/containerd/containerd@v1.7.32" (or higher) and then "go mod tidy" to
refresh go.sum and ensure the indirect upgrade is applied across the module
graph.

In `@internal/pkg/archive/image-blob-gatherer_test.go`:
- Line 293: The test currently hard-codes the full transport error string in the
assert.Equal call (the assert.Equal(t, "...", err.Error()) in
image-blob-gatherer_test.go), which is brittle across environments; change it to
use substring/contains assertions (e.g., assert.Contains or strings.Contains on
err.Error()) to check key phrases such as "error when creating a new image
source", "fetching manifest latest", and "connection refused" (or "pinging
container registry") instead of the full message so the test is robust to
platform-specific network wording.

In `@internal/pkg/mirror/mirror.go`:
- Around line 359-368: The loop that parses multiArch tokens (using variable
multiArch and splitting into parts) currently only checks len(parts)==2 and can
accept empty or whitespace OS/ARCH; update the validation in that block (the
code that builds copy.InstancePlatformFilter) to trim and reject empty parts and
enforce an allow-list pattern for OS and Architecture (e.g., only
letters/numbers, '-', '_' and dots) before appending to platforms; on invalid
tokens return the same error path (the fmt.Errorf currently returned alongside
copy.CopySystemImage) so malformed tokens are rejected at this trust boundary.

In `@internal/pkg/operator/filtered_collector.go`:
- Around line 251-255: The computed platforms slice is passed into
getRelatedImagesFromCatalog but not applied to the operator catalog image copy,
so the catalog copy ends up unfiltered; update the code that assembles or
creates the operator catalog image later in this function to accept and use the
platforms filter (e.g., pass the platforms variable into the catalog image
creation call or set the Platforms field on the catalog image struct before
invoking the copy), ensuring the same platforms value used with
o.ctlgHandler.getRelatedImagesFromCatalog(result.DeclConfig, platforms,
copyImageSchemaMap) is also applied to the operator catalog image copy.

In `@internal/pkg/release/local_stored_collector.go`:
- Around line 56-73: getPlatformFilters in LocalStorageCollector currently
returns nil when both o.Config.Mirror.Platform.Platforms and Architectures are
empty, causing "mirror all" behavior; change it to return a pointer to a slice
containing the default "linux/amd64" string instead. Specifically, in
getPlatformFilters (and where it falls back from Architectures), replace the
final "return nil" with creation of a []string{"linux/amd64"} and return &slice
so callers expecting *[]string receive the documented default platform.

---

Outside diff comments:
In `@internal/pkg/api/v2alpha1/type_config.go`:
- Around line 116-130: Platform.DeepCopy() currently initializes platformCopy
with only Graph, causing scalar fields like Release and KubeVirtContainer to be
lost; update Platform.DeepCopy to copy all scalar and value fields from the
receiver (e.g., set platformCopy.Release = p.Release and
platformCopy.KubeVirtContainer = p.KubeVirtContainer) in addition to the
existing deep copies of slices (Channels, Architectures, Platforms) so the
returned copy is complete; locate the DeepCopy method on type Platform and add
assignments for any other scalar/value fields to mirror the source struct.

---

Nitpick comments:
In `@internal/pkg/operator/catalog_handler_test.go`:
- Line 43: Add a test entry in catalog_handler_test.go that calls
handler.getRelatedImagesFromCatalog with a non-nil platforms argument (e.g., a
slice like []string{"linux/amd64"}) in addition to the existing nil-case; call
the same function used in the diff (getRelatedImagesFromCatalog) with that
non-nil platforms and copyImageSchemaMap, assert err == nil, and verify that
each returned related image in res has its Platforms (or equivalent field) set
to the same non-nil slice (or contains the provided platforms), thereby
validating propagation of the platforms parameter through the function.

In `@internal/pkg/operator/filtered_collector_test.go`:
- Around line 1052-1061: The mock getRelatedImagesFromCatalog in MockHandler
ignores the new platforms parameter; update it to validate the forwarded
platforms pointer (the platforms *[]string argument) before returning results —
e.g., check platforms is not nil and that *platforms matches the expected slice
(or contains required entries) and return an error (or fail) when it does not so
tests detect propagation failures; keep the existing relatedImages behavior
otherwise and leave copyImageSchemaMap untouched.
🪄 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: 1c8d1649-2c7f-439b-a8ae-62be25a36dee

📥 Commits

Reviewing files that changed from the base of the PR and between 192a13d and 9f7c059.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • go.mod
  • internal/pkg/additional/local_stored_collector.go
  • internal/pkg/api/v2alpha1/type_config.go
  • internal/pkg/api/v2alpha1/type_internal.go
  • internal/pkg/api/v2alpha1/type_platform.go
  • internal/pkg/archive/image-blob-gatherer_test.go
  • internal/pkg/batch/concurrent_chan_worker.go
  • internal/pkg/helm/local_stored_collector.go
  • internal/pkg/mirror/mirror.go
  • internal/pkg/mirror/mirror_test.go
  • internal/pkg/mirror/options.go
  • internal/pkg/operator/catalog_handler.go
  • internal/pkg/operator/catalog_handler_test.go
  • internal/pkg/operator/filtered_collector.go
  • internal/pkg/operator/filtered_collector_test.go
  • internal/pkg/operator/interface.go
  • internal/pkg/release/local_stored_collector.go

Comment thread go.mod Outdated
Comment thread go.mod Outdated
Comment thread go.mod Outdated
Comment thread internal/pkg/archive/image-blob-gatherer_test.go Outdated
Comment on lines +359 to +368
for _, platform := range strings.Split(multiArch, ",") {
platform = strings.TrimSpace(platform)
parts := strings.Split(platform, "/")
if len(parts) != 2 {
return copy.CopySystemImage, nil, fmt.Errorf("unknown multi-arch option %q. Choose one of the supported options: 'system', 'all', 'index-only', or a comma-separated platform list like 'linux/amd64,linux/arm64'", multiArch)
}
platforms = append(platforms, copy.InstancePlatformFilter{
OS: parts[0],
Architecture: parts[1],
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Harden platform token validation before constructing filters.

Line 362 only validates len(parts) == 2; empty or whitespace OS/ARCH values still pass. Reject invalid tokens at this boundary to avoid propagating malformed platform filters.

Proposed fix
-		for _, platform := range strings.Split(multiArch, ",") {
+		for _, platform := range strings.Split(multiArch, ",") {
 			platform = strings.TrimSpace(platform)
-			parts := strings.Split(platform, "/")
-			if len(parts) != 2 {
+			parts := strings.SplitN(platform, "/", 2)
+			if len(parts) != 2 {
 				return copy.CopySystemImage, nil, fmt.Errorf("unknown multi-arch option %q. Choose one of the supported options: 'system', 'all', 'index-only', or a comma-separated platform list like 'linux/amd64,linux/arm64'", multiArch)
 			}
+			osPart := strings.TrimSpace(parts[0])
+			archPart := strings.TrimSpace(parts[1])
+			if osPart == "" || archPart == "" {
+				return copy.CopySystemImage, nil, fmt.Errorf("invalid platform %q: expected non-empty os/architecture", platform)
+			}
 			platforms = append(platforms, copy.InstancePlatformFilter{
-				OS:           parts[0],
-				Architecture: parts[1],
+				OS:           osPart,
+				Architecture: archPart,
 			})
 		}

As per coding guidelines, “Validate at trust boundaries with allow-lists, not deny-lists.”

📝 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
for _, platform := range strings.Split(multiArch, ",") {
platform = strings.TrimSpace(platform)
parts := strings.Split(platform, "/")
if len(parts) != 2 {
return copy.CopySystemImage, nil, fmt.Errorf("unknown multi-arch option %q. Choose one of the supported options: 'system', 'all', 'index-only', or a comma-separated platform list like 'linux/amd64,linux/arm64'", multiArch)
}
platforms = append(platforms, copy.InstancePlatformFilter{
OS: parts[0],
Architecture: parts[1],
})
for _, platform := range strings.Split(multiArch, ",") {
platform = strings.TrimSpace(platform)
parts := strings.SplitN(platform, "/", 2)
if len(parts) != 2 {
return copy.CopySystemImage, nil, fmt.Errorf("unknown multi-arch option %q. Choose one of the supported options: 'system', 'all', 'index-only', or a comma-separated platform list like 'linux/amd64,linux/arm64'", multiArch)
}
osPart := strings.TrimSpace(parts[0])
archPart := strings.TrimSpace(parts[1])
if osPart == "" || archPart == "" {
return copy.CopySystemImage, nil, fmt.Errorf("invalid platform %q: expected non-empty os/architecture", platform)
}
platforms = append(platforms, copy.InstancePlatformFilter{
OS: osPart,
Architecture: archPart,
})
}
🤖 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 `@internal/pkg/mirror/mirror.go` around lines 359 - 368, The loop that parses
multiArch tokens (using variable multiArch and splitting into parts) currently
only checks len(parts)==2 and can accept empty or whitespace OS/ARCH; update the
validation in that block (the code that builds copy.InstancePlatformFilter) to
trim and reject empty parts and enforce an allow-list pattern for OS and
Architecture (e.g., only letters/numbers, '-', '_' and dots) before appending to
platforms; on invalid tokens return the same error path (the fmt.Errorf
currently returned alongside copy.CopySystemImage) so malformed tokens are
rejected at this trust boundary.

Source: Coding guidelines

Comment thread internal/pkg/operator/filtered_collector.go
Comment on lines +56 to +73
func (o LocalStorageCollector) getPlatformFilters() *[]string {
// Use new Platforms field if available
if len(o.Config.Mirror.Platform.Platforms) > 0 {
return v2alpha1.ConvertPlatformsToStringSlice(o.Config.Mirror.Platform.Platforms)
}

// Fall back to deprecated Architectures field (assume linux)
if len(o.Config.Mirror.Platform.Architectures) > 0 {
platformStrs := make([]string, len(o.Config.Mirror.Platform.Architectures))
for i, arch := range o.Config.Mirror.Platform.Architectures {
platformStrs[i] = "linux/" + arch
}
return &platformStrs
}

// No platforms specified - mirror all
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Default platform behavior conflicts with config contract.

When both Platforms and Architectures are empty, Line 72 returns nil (mirror all). But Platform docs declare the default should be linux/amd64. This mismatch can unexpectedly mirror every platform.

Proposed fix
 func (o LocalStorageCollector) getPlatformFilters() *[]string {
@@
 	// No platforms specified - mirror all
-	return nil
+	defaultPlatform := []string{"linux/" + v2alpha1.DefaultPlatformArchitecture}
+	return &defaultPlatform
 }
📝 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 (o LocalStorageCollector) getPlatformFilters() *[]string {
// Use new Platforms field if available
if len(o.Config.Mirror.Platform.Platforms) > 0 {
return v2alpha1.ConvertPlatformsToStringSlice(o.Config.Mirror.Platform.Platforms)
}
// Fall back to deprecated Architectures field (assume linux)
if len(o.Config.Mirror.Platform.Architectures) > 0 {
platformStrs := make([]string, len(o.Config.Mirror.Platform.Architectures))
for i, arch := range o.Config.Mirror.Platform.Architectures {
platformStrs[i] = "linux/" + arch
}
return &platformStrs
}
// No platforms specified - mirror all
return nil
}
func (o LocalStorageCollector) getPlatformFilters() *[]string {
// Use new Platforms field if available
if len(o.Config.Mirror.Platform.Platforms) > 0 {
return v2alpha1.ConvertPlatformsToStringSlice(o.Config.Mirror.Platform.Platforms)
}
// Fall back to deprecated Architectures field (assume linux)
if len(o.Config.Mirror.Platform.Architectures) > 0 {
platformStrs := make([]string, len(o.Config.Mirror.Platform.Architectures))
for i, arch := range o.Config.Mirror.Platform.Architectures {
platformStrs[i] = "linux/" + arch
}
return &platformStrs
}
// No platforms specified - mirror all
defaultPlatform := []string{"linux/" + v2alpha1.DefaultPlatformArchitecture}
return &defaultPlatform
}
🤖 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 `@internal/pkg/release/local_stored_collector.go` around lines 56 - 73,
getPlatformFilters in LocalStorageCollector currently returns nil when both
o.Config.Mirror.Platform.Platforms and Architectures are empty, causing "mirror
all" behavior; change it to return a pointer to a slice containing the default
"linux/amd64" string instead. Specifically, in getPlatformFilters (and where it
falls back from Architectures), replace the final "return nil" with creation of
a []string{"linux/amd64"} and return &slice so callers expecting *[]string
receive the documented default platform.

@aguidirh aguidirh force-pushed the CLID-290 branch 2 times, most recently from 3219c3e to 74ce905 Compare June 8, 2026 15:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
internal/pkg/mirror/mirror.go (1)

368-377: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Strengthen platform token validation at this trust boundary.

The current validation only checks len(parts) == 2 and doesn't validate the content of OS/Architecture tokens after splitting. Empty or whitespace-only values (e.g., " / ", "linux/ ") can pass validation and create malformed platform filters. As per coding guidelines, input validation at trust boundaries should use allow-lists, not just structural checks.

🛡️ Proposed fix to validate and sanitize platform tokens
 		for _, platform := range strings.Split(multiArch, ",") {
 			platform = strings.TrimSpace(platform)
-			parts := strings.Split(platform, "/")
+			parts := strings.SplitN(platform, "/", 2)
 			if len(parts) != 2 {
 				return copy.CopySystemImage, nil, fmt.Errorf("unknown multi-arch option %q. Choose one of the supported options: 'system', 'all', 'index-only', or a comma-separated platform list like 'linux/amd64,linux/arm64'", multiArch)
 			}
+			osPart := strings.TrimSpace(parts[0])
+			archPart := strings.TrimSpace(parts[1])
+			if osPart == "" || archPart == "" {
+				return copy.CopySystemImage, nil, fmt.Errorf("invalid platform %q: OS and architecture must be non-empty", platform)
+			}
+			// Validate against allow-list: alphanumeric, hyphen, underscore, dot
+			validPattern := regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
+			if !validPattern.MatchString(osPart) || !validPattern.MatchString(archPart) {
+				return copy.CopySystemImage, nil, fmt.Errorf("invalid platform %q: OS and architecture must contain only letters, numbers, hyphens, underscores, and dots", platform)
+			}
 			platforms = append(platforms, copy.InstancePlatformFilter{
-				OS:           parts[0],
-				Architecture: parts[1],
+				OS:           osPart,
+				Architecture: archPart,
 			})
 		}

Note: You'll need to import "regexp" at the top of the file.

🤖 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 `@internal/pkg/mirror/mirror.go` around lines 368 - 377, The loop that parses
multiArch platforms accepts tokens with only a structural check (len(parts)==2)
which allows empty or whitespace OS/Architecture values; update the logic in the
function containing this loop (the multi-arch platform parsing block in
mirror.go) to (1) Trim and validate each token and each part after splitting,
(2) enforce an allow-list/regex for OS and Architecture (e.g., permit only
lowercase letters, digits and hyphens/underscores) and reject empty strings, and
(3) return a clear error when a token fails validation; add the required import
("regexp") and use the validated parts when building copy.InstancePlatformFilter
to prevent malformed platform filters.

Source: Coding guidelines

🤖 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.

Duplicate comments:
In `@internal/pkg/mirror/mirror.go`:
- Around line 368-377: The loop that parses multiArch platforms accepts tokens
with only a structural check (len(parts)==2) which allows empty or whitespace
OS/Architecture values; update the logic in the function containing this loop
(the multi-arch platform parsing block in mirror.go) to (1) Trim and validate
each token and each part after splitting, (2) enforce an allow-list/regex for OS
and Architecture (e.g., permit only lowercase letters, digits and
hyphens/underscores) and reject empty strings, and (3) return a clear error when
a token fails validation; add the required import ("regexp") and use the
validated parts when building copy.InstancePlatformFilter to prevent malformed
platform filters.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: 6601800d-c8a5-472a-8855-b3444425902e

📥 Commits

Reviewing files that changed from the base of the PR and between 9f7c059 and 3219c3e.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • go.mod
  • internal/pkg/additional/local_stored_collector.go
  • internal/pkg/api/v2alpha1/type_config.go
  • internal/pkg/api/v2alpha1/type_internal.go
  • internal/pkg/api/v2alpha1/type_platform.go
  • internal/pkg/archive/image-blob-gatherer_test.go
  • internal/pkg/batch/concurrent_chan_worker.go
  • internal/pkg/helm/local_stored_collector.go
  • internal/pkg/mirror/mirror.go
  • internal/pkg/mirror/mirror_test.go
  • internal/pkg/mirror/options.go
  • internal/pkg/operator/catalog_handler.go
  • internal/pkg/operator/catalog_handler_test.go
  • internal/pkg/operator/filtered_collector.go
  • internal/pkg/operator/filtered_collector_test.go
  • internal/pkg/operator/interface.go
  • internal/pkg/release/local_stored_collector.go
✅ Files skipped from review due to trivial changes (1)
  • internal/pkg/archive/image-blob-gatherer_test.go
🚧 Files skipped from review as they are similar to previous changes (12)
  • internal/pkg/operator/filtered_collector_test.go
  • internal/pkg/operator/interface.go
  • internal/pkg/operator/catalog_handler.go
  • internal/pkg/api/v2alpha1/type_platform.go
  • internal/pkg/operator/catalog_handler_test.go
  • internal/pkg/batch/concurrent_chan_worker.go
  • internal/pkg/additional/local_stored_collector.go
  • internal/pkg/operator/filtered_collector.go
  • internal/pkg/helm/local_stored_collector.go
  • internal/pkg/api/v2alpha1/type_internal.go
  • internal/pkg/api/v2alpha1/type_config.go
  • internal/pkg/release/local_stored_collector.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
go.mod (1)

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

Resolve known vulnerable pinned modules before merge.

go.mod still pins versions flagged as HIGH severity:

  • Line 8: github.com/distribution/distribution/v3 v3.0.0
  • Line 63: github.com/containerd/containerd v1.7.29
  • Line 138: github.com/moby/spdystream v0.5.0

Please bump to patched releases and refresh go.sum (go get ... && go mod tidy) to satisfy the repo’s dependency security policy.

#!/bin/bash
set -euo pipefail

echo "== Current versions in go.mod =="
rg -n 'github.com/distribution/distribution/v3|github.com/containerd/containerd|github.com/moby/spdystream' go.mod

echo
echo "== OSV check: distribution/v3 =="
curl -s https://api.osv.dev/v1/query -H 'Content-Type: application/json' \
  -d '{"package":{"ecosystem":"Go","name":"github.com/distribution/distribution/v3"}}' \
  | jq '.vulns[]? | {id: .id, summary: .summary, affected: .affected[0].ranges}'

echo
echo "== OSV check: containerd =="
curl -s https://api.osv.dev/v1/query -H 'Content-Type: application/json' \
  -d '{"package":{"ecosystem":"Go","name":"github.com/containerd/containerd"}}' \
  | jq '.vulns[]? | {id: .id, summary: .summary, affected: .affected[0].ranges}'

echo
echo "== OSV check: moby/spdystream =="
curl -s https://api.osv.dev/v1/query -H 'Content-Type: application/json' \
  -d '{"package":{"ecosystem":"Go","name":"github.com/moby/spdystream"}}' \
  | jq '.vulns[]? | {id: .id, summary: .summary, affected: .affected[0].ranges}'

Also applies to: 63-63, 138-138

🤖 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 `@go.mod` at line 8, go.mod pins vulnerable module versions for
github.com/distribution/distribution/v3, github.com/containerd/containerd and
github.com/moby/spdystream; update each to the latest patched release, run “go
get github.com/distribution/distribution/v3@<patched>
github.com/containerd/containerd@<patched> github.com/moby/spdystream@<patched>”
(replace <patched> with the secure versions) and then run “go mod tidy” to
refresh go.sum; ensure the updated module lines
(github.com/distribution/distribution/v3, github.com/containerd/containerd,
github.com/moby/spdystream) in go.mod reflect the new versions and commit the
resulting go.mod and go.sum changes.

Sources: Coding guidelines, Linters/SAST tools

🤖 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.

Duplicate comments:
In `@go.mod`:
- Line 8: go.mod pins vulnerable module versions for
github.com/distribution/distribution/v3, github.com/containerd/containerd and
github.com/moby/spdystream; update each to the latest patched release, run “go
get github.com/distribution/distribution/v3@<patched>
github.com/containerd/containerd@<patched> github.com/moby/spdystream@<patched>”
(replace <patched> with the secure versions) and then run “go mod tidy” to
refresh go.sum; ensure the updated module lines
(github.com/distribution/distribution/v3, github.com/containerd/containerd,
github.com/moby/spdystream) in go.mod reflect the new versions and commit the
resulting go.mod and go.sum changes.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: fc19da77-8286-4674-9b7f-072db74d31b2

📥 Commits

Reviewing files that changed from the base of the PR and between 3219c3e and 74ce905.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • go.mod
  • internal/pkg/additional/local_stored_collector.go
  • internal/pkg/api/v2alpha1/type_config.go
  • internal/pkg/api/v2alpha1/type_internal.go
  • internal/pkg/api/v2alpha1/type_platform.go
  • internal/pkg/archive/image-blob-gatherer_test.go
  • internal/pkg/batch/concurrent_chan_worker.go
  • internal/pkg/helm/local_stored_collector.go
  • internal/pkg/mirror/mirror.go
  • internal/pkg/mirror/mirror_test.go
  • internal/pkg/mirror/options.go
  • internal/pkg/operator/catalog_handler.go
  • internal/pkg/operator/catalog_handler_test.go
  • internal/pkg/operator/filtered_collector.go
  • internal/pkg/operator/filtered_collector_test.go
  • internal/pkg/operator/interface.go
  • internal/pkg/release/local_stored_collector.go
✅ Files skipped from review due to trivial changes (1)
  • internal/pkg/archive/image-blob-gatherer_test.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • internal/pkg/operator/catalog_handler_test.go
  • internal/pkg/operator/interface.go
  • internal/pkg/mirror/options.go
  • internal/pkg/operator/filtered_collector.go
  • internal/pkg/additional/local_stored_collector.go
  • internal/pkg/batch/concurrent_chan_worker.go
  • internal/pkg/mirror/mirror_test.go
  • internal/pkg/operator/filtered_collector_test.go
  • internal/pkg/api/v2alpha1/type_platform.go
  • internal/pkg/api/v2alpha1/type_config.go
  • internal/pkg/release/local_stored_collector.go
  • internal/pkg/helm/local_stored_collector.go
  • internal/pkg/api/v2alpha1/type_internal.go
  • internal/pkg/operator/catalog_handler.go
  • internal/pkg/mirror/mirror.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
internal/pkg/operator/filtered_collector.go (1)

251-255: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Catalog image misses platform filter propagation

Line 306 builds the catalog RelatedImage without Platforms, so the catalog copy path can still mirror all architectures even though Line 254 now filters related images.

Suggested patch
 	relatedImages[componentName] = []v2alpha1.RelatedImage{
 		{
 			Name:          catalogName,
 			Image:         catalogImage,
 			Type:          v2alpha1.TypeOperatorCatalog,
 			TargetTag:     targetTag,
 			TargetCatalog: op.TargetCatalog,
 			RebuiltTag:    rebuiltTag,
 			FullCatalog:   isFullCatalog(op),
+			Platforms:     platforms,
 		},
 	}

Also applies to: 306-315

🤖 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 `@internal/pkg/operator/filtered_collector.go` around lines 251 - 255, The
catalog-related image construction is missing propagation of platform filters:
after you call o.ctlgHandler.getRelatedImagesFromCatalog(result.DeclConfig,
platforms, copyImageSchemaMap) (where platforms :=
v2alpha1.ConvertPlatformsToStringSlice(op.Platforms)), ensure that each
RelatedImage created for the catalog copy path includes the same Platforms value
(set RelatedImage.Platforms = platforms or equivalent) so the catalog copy
honors op.Platforms; update the code paths that build RelatedImage instances
(the block referenced around getRelatedImagesFromCatalog and the construction in
the 306–315 region) to copy/set the platforms slice from the platforms variable
into the RelatedImage before returning or appending.
🤖 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 `@go.mod`:
- Line 3: The go.mod specifies Go 1.25.6 but the build sandbox uses
GO_VERSION=go1.23.5 causing mismatched toolchains; update the hack/build.sh,
hack/e2e.sh and the Dockerfile (local/go-toolset) to either install/use Go
1.25.6 (set GO_VERSION=go1.25.6 and ensure the local/go-toolset image installs
that toolchain) or make the scripts export GOTOOLCHAIN=auto so the runtime
respects the go.mod directive; change occurrences of GO_VERSION and any image
build steps that pull the older toolset, and verify createToolchain/install
steps in local/go-toolset use the 1.25.6 installer to keep builds consistent
with go.mod.

---

Duplicate comments:
In `@internal/pkg/operator/filtered_collector.go`:
- Around line 251-255: The catalog-related image construction is missing
propagation of platform filters: after you call
o.ctlgHandler.getRelatedImagesFromCatalog(result.DeclConfig, platforms,
copyImageSchemaMap) (where platforms :=
v2alpha1.ConvertPlatformsToStringSlice(op.Platforms)), ensure that each
RelatedImage created for the catalog copy path includes the same Platforms value
(set RelatedImage.Platforms = platforms or equivalent) so the catalog copy
honors op.Platforms; update the code paths that build RelatedImage instances
(the block referenced around getRelatedImagesFromCatalog and the construction in
the 306–315 region) to copy/set the platforms slice from the platforms variable
into the RelatedImage before returning or appending.
🪄 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: 3d59dc56-7672-41f8-a6be-eaf1d8c7f04a

📥 Commits

Reviewing files that changed from the base of the PR and between 74ce905 and 9074ce3.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • go.mod
  • internal/pkg/additional/local_stored_collector.go
  • internal/pkg/api/v2alpha1/type_config.go
  • internal/pkg/api/v2alpha1/type_internal.go
  • internal/pkg/api/v2alpha1/type_platform.go
  • internal/pkg/archive/image-blob-gatherer_test.go
  • internal/pkg/batch/concurrent_chan_worker.go
  • internal/pkg/helm/local_stored_collector.go
  • internal/pkg/mirror/mirror.go
  • internal/pkg/mirror/mirror_test.go
  • internal/pkg/mirror/options.go
  • internal/pkg/operator/catalog_handler.go
  • internal/pkg/operator/catalog_handler_test.go
  • internal/pkg/operator/filtered_collector.go
  • internal/pkg/operator/filtered_collector_test.go
  • internal/pkg/operator/interface.go
  • internal/pkg/release/local_stored_collector.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • internal/pkg/mirror/options.go
  • internal/pkg/archive/image-blob-gatherer_test.go
  • internal/pkg/additional/local_stored_collector.go
  • internal/pkg/batch/concurrent_chan_worker.go
  • internal/pkg/operator/catalog_handler_test.go
  • internal/pkg/api/v2alpha1/type_platform.go
  • internal/pkg/api/v2alpha1/type_internal.go
  • internal/pkg/operator/interface.go
  • internal/pkg/mirror/mirror_test.go
  • internal/pkg/operator/catalog_handler.go
  • internal/pkg/release/local_stored_collector.go
  • internal/pkg/mirror/mirror.go
  • internal/pkg/api/v2alpha1/type_config.go

Comment thread go.mod Outdated
module github.com/openshift/oc-mirror/v2

go 1.24.3
go 1.25.6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== go.mod go directive =="
rg -n '^go ' go.mod

echo
echo "== toolchain directives (if any) =="
rg -n '^toolchain ' go.mod || true

echo
echo "== Go version pins in common repo files =="
rg -n '1\.25|go-version|GOTOOLCHAIN|golang' Makefile .golangci.v2.yaml .golangci.yaml .github || true

echo
echo "== Dockerfiles / CI configs that may pin Go versions =="
fd -i 'Dockerfile|\.ya?ml$|\.json$' .github | xargs -r rg -n 'golang:|go1\.|1\.25|GOTOOLCHAIN|actions/setup-go' || true

Repository: openshift/oc-mirror

Length of output: 313


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for setup-go go-version pins =="
fd -i '*.yml' .github/workflows .github 2>/dev/null | xargs -r rg -n 'actions/setup-go|go-version:|go-version|GOTOOLCHAIN|toolchain:|GO_VERSION' || true

echo
echo "== Search for GOTOOLCHAIN / go1.25 across repo (limited to text/config files) =="
fd -i '*.*' . | rg -n --glob '!**/vendor/**' --glob '!**/node_modules/**' --glob '!**/dist/**' 'GOTOOLCHAIN|go1\.25|go-version|GO_VERSION' || true

echo
echo "== Check Makefile toolchain behavior (go env / go version) =="
rg -n 'GOTOOLCHAIN|GO_VERSION|setup-go|go1\.|golangci-lint|go env|toolchain|GOROOT' Makefile .github  || true

echo
echo "== Any local toolchain files / scripts? =="
ls -la | rg -n 'go\.version|toolchain|GOTOOLCHAIN|\.go-version' || true
fd -i -t f 'go\.version|\.go-version|toolchain.*|*go*version*' .github Makefile . || true

Repository: openshift/oc-mirror

Length of output: 1223


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== actions/setup-go usage in workflows =="
rg -n --no-heading 'actions/setup-go' .github/workflows .github 2>/dev/null || true

echo
echo "== go-version / Go version pins in workflows (yaml/yml) =="
rg -n --no-heading 'go-version:|GOTOOLCHAIN|toolchain:|GO_VERSION' .github/workflows .github 2>/dev/null || true

echo
echo "== Look for GOTOOLCHAIN / GO_VERSION elsewhere (excluding vendor) =="
rg -n --no-heading 'GOTOOLCHAIN|GO_VERSION' . --glob '!**/vendor/**' --glob '!**/node_modules/**' 2>/dev/null || true

echo
echo "== Check for Go toolchain files by name =="
fd -i -t f 'go\.version|\.go-version|tool-versions|\.tool-versions|asdf\.tool-versions' . 2>/dev/null || true

echo
echo "== Check Makefile for any GOTOOLCHAIN/Go toolchain logic =="
rg -n --no-heading 'GOTOOLCHAIN|GO_VERSION|setup-go|go-version:|go env|GOROOT' Makefile 2>/dev/null || true

Repository: openshift/oc-mirror

Length of output: 1206


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for GO_VERSION / go1.23.5 / go1.25 in repo =="
rg -n --no-heading 'GO_VERSION=|go1\.23\.5|go1\.25|1\.25\.6|GOTOOLCHAIN' . --glob '!**/vendor/**' --glob '!**/node_modules/**' || true

echo
echo "== Inspect Dockerfile around GO_VERSION pin (lines ~1-80) =="
nl -ba Dockerfile | sed -n '1,120p'

echo
echo "== Inspect tests/e2e/Makefile =="
nl -ba tests/e2e/Makefile | sed -n '1,80p'

echo
echo "== Search workflows for Dockerfile usage (build/push) =="
rg -n --no-heading 'Dockerfile|docker build|build-args|GO_VERSION|go1\.23|go1\.25|GOTOOLCHAIN' .github/workflows .github 2>/dev/null || true

echo
echo "== Inspect Makefile for any go/toolchain pin logic =="
nl -ba Makefile | sed -n '1,120p'

Repository: openshift/oc-mirror

Length of output: 1031


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Show relevant Dockerfile section (around GO_VERSION pin) =="
sed -n '1,120p' Dockerfile

echo
echo "== Show relevant Dockerfile.tests-extension section (if small) =="
sed -n '1,120p' Dockerfile.tests-extension || true

echo
echo "== Show relevant images/cli/Dockerfile.ci section (if small) =="
sed -n '1,140p' images/cli/Dockerfile.ci || true

echo
echo "== Show tests/e2e/Makefile (small) =="
sed -n '1,120p' tests/e2e/Makefile || true

echo
echo "== Show Makefile top section =="
sed -n '1,120p' Makefile

Repository: openshift/oc-mirror

Length of output: 11260


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== List workflows =="
ls -ლა .github/workflows 2>/dev/null || true

echo
echo "== Search workflows for oc-mirror image build (Dockerfiles referenced) =="
rg -n --no-heading 'Dockerfile\.ci|images/cli/Dockerfile\.ci|Dockerfile\.tests-extension|Dockerfile\.tests|Dockerfile[^/]*\b|docker build|buildx|ko |go-toolset|local/go-toolset' .github/workflows 2>/dev/null || true

echo
echo "== Search workflows for build machinery / make targets =="
rg -n --no-heading 'make (build|sanity|verify|test|test-unit)|sanity|golangci-lint|GOTOOLCHAIN|GO_VERSION|setup-go' .github/workflows 2>/dev/null || true

Repository: openshift/oc-mirror

Length of output: 218


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== List .github directory (top-level) =="
find .github -maxdepth 2 -type f -print 2>/dev/null | sed -n '1,200p' || true

echo
echo "== Search for CI build config files (yaml/yml) =="
fd -i -t f '*.yml' -a -a .github 2>/dev/null || true
fd -i -t f '*.yaml' .github 2>/dev/null || true

echo
echo "== Search entire repo for setup-go / go-toolset / builder image tags =="
rg -n --no-heading 'actions/setup-go|go-toolset|Dockerfile\.ci|golang-1\.2|golang-1\.23|golang-1\.24|golang-1\.25|GOTOOLCHAIN=auto|GO_VERSION=go' . --glob '!**/vendor/**' --glob '!**/node_modules/**' 2>/dev/null || true

Repository: openshift/oc-mirror

Length of output: 2658


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== hack/build.sh =="
sed -n '1,160p' hack/build.sh

echo
echo "== hack/e2e.sh =="
sed -n '1,160p' hack/e2e.sh

echo
echo "== tests/integration/image-builders/release/containerfile-rhel9 =="
sed -n '1,120p' tests/integration/image-builders/release/containerfile-rhel9

echo
echo "== List any other integration image-builder Dockerfiles mentioning golang-1.2* =="
rg -n --no-heading 'golang-1\.23|golang-1\.24|golang-1\.25' tests/integration/image-builders --glob '!**/vendor/**' 2>/dev/null || true

Repository: openshift/oc-mirror

Length of output: 2585


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== go.mod excerpt around lines 1-200 =="
sed -n '1,220p' go.mod

echo
echo "== go.mod lines with the specific modules mentioned in the review (if present) =="
rg -n 'github.com/distribution/distribution/v3|github.com/containerd/containerd|github.com/moby/spdystream' go.mod || true

Repository: openshift/oc-mirror

Length of output: 11645


Align local Go toolchain with go.mod (Go 1.25.6 vs GO_VERSION=go1.23.5)

  • go.mod requires Go 1.25.6, but the local builder used by hack/build.sh and hack/e2e.sh (Dockerfilelocal/go-toolset) downloads GO_VERSION=go1.23.5 and those hack flows don’t set GOTOOLCHAIN=auto, so make build can fail when enforcing the go directive.
  • Update Dockerfile/hack scripts to install or enable the Go 1.25.6 toolchain consistently (alternatively set GOTOOLCHAIN=auto for the hack builds).
🤖 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 `@go.mod` at line 3, The go.mod specifies Go 1.25.6 but the build sandbox uses
GO_VERSION=go1.23.5 causing mismatched toolchains; update the hack/build.sh,
hack/e2e.sh and the Dockerfile (local/go-toolset) to either install/use Go
1.25.6 (set GO_VERSION=go1.25.6 and ensure the local/go-toolset image installs
that toolchain) or make the scripts export GOTOOLCHAIN=auto so the runtime
respects the go.mod directive; change occurrences of GO_VERSION and any image
build steps that pull the older toolset, and verify createToolchain/install
steps in local/go-toolset use the 1.25.6 installer to keep builds consistent
with go.mod.

@adolfo-ab adolfo-ab 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.

Can you check if you need to also update internal/pkg/config/defaults.go and cincinnati.go?

Also, maybe you could add one or two integration tests, so that this feature is already covered.


// Fall back to deprecated Architectures field (assume linux)
//nolint:staticcheck // SA1019: Architectures is deprecated but we maintain backward compatibility
if len(o.Config.Mirror.Platform.Architectures) > 0 {

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.

Since Architectures is deprecated, maybe we can add a log line mentioning it here, and asking to consider using Platform instead

// InstancePlatformFilter defines OS and Architecture for filtering
// multi-architecture manifest lists. This allows selecting specific
// platforms (e.g., linux/amd64, linux/arm64) when mirroring images.
type InstancePlatformFilter struct {

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.

What happens if a user only sets the OS or the Architecture? Should we check that we always have both? Otherwise we might get linux/ or /amd64, no?

Implement InstancePlatformFilter feature with sparse manifest support to
allow users to specify OS/Architecture pairs for filtering multi-architecture
images across all image types (additional images, operators, helm charts,
and releases).

This feature brings the platform filtering capability from skopeo
(containers/image PR #2874) to oc-mirror, enabling users to mirror only
specific platforms instead of all architectures. The implementation uses
sparse manifests where the manifest list references all platforms but only
the specified platforms have their actual layer blobs downloaded, significantly
reducing disk usage and bandwidth.

Platform filtering is applied during Mirror-to-Disk and Mirror-to-Mirror
workflows. Disk-to-Mirror workflow uses already-filtered images from disk
without re-applying platform filters.

The implementation includes backward compatibility with the deprecated
Platform.Architectures field and follows the same strategy as skopeo
for consistency.

Fixes: CLID-290
Signed-off-by: Alex Guidi <aguidi@redhat.com>
@aguidirh

Copy link
Copy Markdown
Contributor Author

/test v1-e2e

@openshift-ci

openshift-ci Bot commented Jun 18, 2026

Copy link
Copy Markdown

@aguidirh: 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. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants