Skip to content

OCPBUGS-66263: Include index image sub-digests in dry run mapping.txt#1355

Open
dorzel wants to merge 1 commit intoopenshift:mainfrom
dorzel:OCPBUGS-66263
Open

OCPBUGS-66263: Include index image sub-digests in dry run mapping.txt#1355
dorzel wants to merge 1 commit intoopenshift:mainfrom
dorzel:OCPBUGS-66263

Conversation

@dorzel
Copy link
Member

@dorzel dorzel commented Feb 18, 2026

Description

This PR adds functionality to include index image sub-digests in mapping.txt, as part of the linked bug. This facilitates users who manually mirror based on mapping.txt or use automated security scanning which requires all digests to be listed.

Github / Jira issue: https://issues.redhat.com/browse/OCPBUGS-66263

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Code Improvements (Refactoring, Performance, CI upgrades, etc)
  • Internal repo assets (diagrams / docs on github repo)
  • This change requires a documentation update on openshift docs

How Has This Been Tested?

  • Ran unit test suite
  • Manually ran an m2d dry run and inspected the mapping.txt
kind: ImageSetConfiguration
apiVersion: mirror.openshift.io/v2alpha1
mirror:
  additionalImages:
    - name: registry.access.redhat.com/ubi9/ubi-minimal:9.4
    - name: docker.io/library/alpine:3.20
    - name: quay.io/podman/hello:latest
    - name: oci:///tmp/oci-test-image
    - name: registry.access.redhat.com/ubi9/ubi-minimal@sha256:c0e70387664f30cd9cf2795b547e4a9a51002c44a4a86aa9335ab030134bf392

Corresponding mapping.txt:

docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:f5d2c6a1e0c86e4234ea601552dbabb4ced0e013a1efcbfb439f1f6a7a9275b0=docker://localhost:55000/ubi9/ubi-minimal@sha256:f5d2c6a1e0c86e4234ea601552dbabb4ced0e013a1efcbfb439f1f6a7a9275b0
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:96f4394d39e6edb69ca51f000f3e7dfb62990f55868134cfd83c82177651e848=docker://localhost:55000/ubi9/ubi-minimal@sha256:96f4394d39e6edb69ca51f000f3e7dfb62990f55868134cfd83c82177651e848
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:39c59a30e3ecae689c23b27e54a81e03d9a5db22d11890edaf6bb16bac783e8b=docker://localhost:55000/ubi9/ubi-minimal@sha256:39c59a30e3ecae689c23b27e54a81e03d9a5db22d11890edaf6bb16bac783e8b
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:d187f310724694b1daae2f99f6f86ae05b573eed6826fa40d4233e76bd07312e=docker://localhost:55000/ubi9/ubi-minimal@sha256:d187f310724694b1daae2f99f6f86ae05b573eed6826fa40d4233e76bd07312e
docker://docker.io/library/alpine:3.20=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:b0cb30c51c47cdfde647364301758b14c335dea2fddc9490d4f007d67ecb2538=docker://localhost:55000/library/alpine@sha256:b0cb30c51c47cdfde647364301758b14c335dea2fddc9490d4f007d67ecb2538
docker://docker.io/library/alpine:3.20@sha256:66f45812e9b5053abd384c1c6037261e526b1a0a4ac783ecaf3d5e04a94a0412=docker://localhost:55000/library/alpine@sha256:66f45812e9b5053abd384c1c6037261e526b1a0a4ac783ecaf3d5e04a94a0412
docker://docker.io/library/alpine:3.20@sha256:a4e2d43613b6ee71c96f9d26a2e515729fafeb75e4f0dfea524445b3344dccfe=docker://localhost:55000/library/alpine@sha256:a4e2d43613b6ee71c96f9d26a2e515729fafeb75e4f0dfea524445b3344dccfe
docker://docker.io/library/alpine:3.20@sha256:4135f5ea9c06f0857b68f7a6beaa40480d1f11d64e6341d11ed061cce5f66214=docker://localhost:55000/library/alpine@sha256:4135f5ea9c06f0857b68f7a6beaa40480d1f11d64e6341d11ed061cce5f66214
docker://docker.io/library/alpine:3.20@sha256:1e161cda0d91d4d1c28464a134f1c0fef668a7c6e486017314e06a64b875c288=docker://localhost:55000/library/alpine@sha256:1e161cda0d91d4d1c28464a134f1c0fef668a7c6e486017314e06a64b875c288
docker://docker.io/library/alpine:3.20@sha256:a1acb89d2fc6e0d5c1f3b3be4fd4bda3bd29ca8d5df1461b03aabefefa9d4321=docker://localhost:55000/library/alpine@sha256:a1acb89d2fc6e0d5c1f3b3be4fd4bda3bd29ca8d5df1461b03aabefefa9d4321
docker://docker.io/library/alpine:3.20@sha256:9ace55212059e43b0b246bb975a26b4f6fb41f7384bc87b57ad3aae2a6e5e817=docker://localhost:55000/library/alpine@sha256:9ace55212059e43b0b246bb975a26b4f6fb41f7384bc87b57ad3aae2a6e5e817
docker://docker.io/library/alpine:3.20@sha256:13688eb4bf30a8a94d920e2c6489aceadb6caa26325426b558c35af4322f797a=docker://localhost:55000/library/alpine@sha256:13688eb4bf30a8a94d920e2c6489aceadb6caa26325426b558c35af4322f797a
docker://docker.io/library/alpine:3.20@sha256:c4140ad89752b59d3d1567f6c2c5922d4b1a3eb7ab3ac41472772fcc4a77a447=docker://localhost:55000/library/alpine@sha256:c4140ad89752b59d3d1567f6c2c5922d4b1a3eb7ab3ac41472772fcc4a77a447
docker://docker.io/library/alpine:3.20@sha256:0ee208147b93c91fdda2ded4a5daf71c91d2a491e94b56e29c5edc11d16b5f11=docker://localhost:55000/library/alpine@sha256:0ee208147b93c91fdda2ded4a5daf71c91d2a491e94b56e29c5edc11d16b5f11
docker://docker.io/library/alpine:3.20@sha256:98d989819241cb7170e0660f1be3f4e1a6a9e70ba0c4f5c4c7bded2b531e46b6=docker://localhost:55000/library/alpine@sha256:98d989819241cb7170e0660f1be3f4e1a6a9e70ba0c4f5c4c7bded2b531e46b6
docker://docker.io/library/alpine:3.20@sha256:1b8c7e5752cc75d8be572ac609fc4e1e415387acac0e70b80922b8bfc82894f9=docker://localhost:55000/library/alpine@sha256:1b8c7e5752cc75d8be572ac609fc4e1e415387acac0e70b80922b8bfc82894f9
docker://docker.io/library/alpine:3.20@sha256:3e5e9c7e2b11707d02a8dde9f71dacb294c3bce820d013f817c57c52ee141570=docker://localhost:55000/library/alpine@sha256:3e5e9c7e2b11707d02a8dde9f71dacb294c3bce820d013f817c57c52ee141570
docker://docker.io/library/alpine:3.20@sha256:a5588164e8323980b589e3f4a8ca819e50816ec140fcf8f66904ed539047b30b=docker://localhost:55000/library/alpine@sha256:a5588164e8323980b589e3f4a8ca819e50816ec140fcf8f66904ed539047b30b
docker://docker.io/library/alpine:3.20@sha256:7c694b533384ef3d60b9743a8e3d50a8d1586d5253d2a60d227aa5526966a54b=docker://localhost:55000/library/alpine@sha256:7c694b533384ef3d60b9743a8e3d50a8d1586d5253d2a60d227aa5526966a54b
docker://docker.io/library/alpine:3.20@sha256:dfb98350c924a6ea7572103667da653b98c597bf22aadec19f27866c987457fa=docker://localhost:55000/library/alpine@sha256:dfb98350c924a6ea7572103667da653b98c597bf22aadec19f27866c987457fa
docker://quay.io/podman/hello:latest=docker://localhost:55000/podman/hello:latest
docker://quay.io/podman/hello:latest@sha256:43de9874507eaa8ffd88eac885b672b1dfc57cc583d9ad920850f97f19809f8f=docker://localhost:55000/podman/hello@sha256:43de9874507eaa8ffd88eac885b672b1dfc57cc583d9ad920850f97f19809f8f
docker://quay.io/podman/hello:latest@sha256:5c44ef36dc5e35a76904da0e028cf9413e0176a653525162368af13fed03571c=docker://localhost:55000/podman/hello@sha256:5c44ef36dc5e35a76904da0e028cf9413e0176a653525162368af13fed03571c
docker://quay.io/podman/hello:latest@sha256:92be1897bfcf5a8c9e59ce538645d7c159253717fa8edbb964ff5a259a5535ab=docker://localhost:55000/podman/hello@sha256:92be1897bfcf5a8c9e59ce538645d7c159253717fa8edbb964ff5a259a5535ab
docker://quay.io/podman/hello:latest@sha256:6fba1b311cad0201c377469c66c9cdfc4c48ea99a186869c7ce2609e0a013965=docker://localhost:55000/podman/hello@sha256:6fba1b311cad0201c377469c66c9cdfc4c48ea99a186869c7ce2609e0a013965
oci:///tmp/oci-test-image=docker://localhost:55000/tmp/oci-test-image:latest
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:c0e70387664f30cd9cf2795b547e4a9a51002c44a4a86aa9335ab030134bf392=docker://localhost:55000/ubi9/ubi-minimal:sha256-c0e70387664f30cd9cf2795b547e4a9a51002c44a4a86aa9335ab030134bf392
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:f5d2c6a1e0c86e4234ea601552dbabb4ced0e013a1efcbfb439f1f6a7a9275b0=docker://localhost:55000/ubi9/ubi-minimal@sha256:f5d2c6a1e0c86e4234ea601552dbabb4ced0e013a1efcbfb439f1f6a7a9275b0
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:96f4394d39e6edb69ca51f000f3e7dfb62990f55868134cfd83c82177651e848=docker://localhost:55000/ubi9/ubi-minimal@sha256:96f4394d39e6edb69ca51f000f3e7dfb62990f55868134cfd83c82177651e848
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:39c59a30e3ecae689c23b27e54a81e03d9a5db22d11890edaf6bb16bac783e8b=docker://localhost:55000/ubi9/ubi-minimal@sha256:39c59a30e3ecae689c23b27e54a81e03d9a5db22d11890edaf6bb16bac783e8b
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:d187f310724694b1daae2f99f6f86ae05b573eed6826fa40d4233e76bd07312e=docker://localhost:55000/ubi9/ubi-minimal@sha256:d187f310724694b1daae2f99f6f86ae05b573eed6826fa40d4233e76bd07312e

Expected Outcome

All sub-digests of any index image in the mapping.txt are listed directly below it.

Summary by CodeRabbit

  • New Features

    • Dry-run now detects multi-architecture (manifest list) images and emits per-variant sub-digest source→destination mappings; docker destinations are also generated as digest-pinned targets and included in missing-image reporting.
  • Tests

    • Added tests that construct an OCI layout with a manifest list and verify dry-run includes expected per-sub-digest mappings and destination generation.

@openshift-ci-robot openshift-ci-robot added jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Feb 18, 2026
@openshift-ci-robot
Copy link

@dorzel: This pull request references Jira Issue OCPBUGS-66263, which is invalid:

  • expected the bug to target the "4.22.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

Description

This PR adds functionality to include index image sub-digests in mapping.txt, as part of the linked bug. This facilitates users who manually mirror based on mapping.txt or use automated security scanning which requires all digests to be listed.

Github / Jira issue: https://issues.redhat.com/browse/OCPBUGS-66263

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Code Improvements (Refactoring, Performance, CI upgrades, etc)
  • Internal repo assets (diagrams / docs on github repo)
  • This change requires a documentation update on openshift docs

How Has This Been Tested?

  • Ran unit test suite
  • Manually ran an m2d dry run and inspected the mapping.txt
kind: ImageSetConfiguration
apiVersion: mirror.openshift.io/v2alpha1
mirror:
 additionalImages:
   - name: registry.access.redhat.com/ubi9/ubi-minimal:9.4
   - name: docker.io/library/alpine:3.20
   - name: quay.io/podman/hello:latest
   - name: oci:///tmp/oci-test-image
   - name: registry.access.redhat.com/ubi9/ubi-minimal@sha256:b87097994ed62fbf1de5b4c35b4c28e5b654a1cc5e87f0b8a187e5c1f8cba0a0

Corresponding mapping.txt:

docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:f5d2c6a1e0c86e4234ea601552dbabb4ced0e013a1efcbfb439f1f6a7a9275b0=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:96f4394d39e6edb69ca51f000f3e7dfb62990f55868134cfd83c82177651e848=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:39c59a30e3ecae689c23b27e54a81e03d9a5db22d11890edaf6bb16bac783e8b=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:d187f310724694b1daae2f99f6f86ae05b573eed6826fa40d4233e76bd07312e=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://docker.io/library/alpine:3.20=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:b0cb30c51c47cdfde647364301758b14c335dea2fddc9490d4f007d67ecb2538=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:66f45812e9b5053abd384c1c6037261e526b1a0a4ac783ecaf3d5e04a94a0412=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:a4e2d43613b6ee71c96f9d26a2e515729fafeb75e4f0dfea524445b3344dccfe=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:4135f5ea9c06f0857b68f7a6beaa40480d1f11d64e6341d11ed061cce5f66214=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:1e161cda0d91d4d1c28464a134f1c0fef668a7c6e486017314e06a64b875c288=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:a1acb89d2fc6e0d5c1f3b3be4fd4bda3bd29ca8d5df1461b03aabefefa9d4321=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:9ace55212059e43b0b246bb975a26b4f6fb41f7384bc87b57ad3aae2a6e5e817=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:13688eb4bf30a8a94d920e2c6489aceadb6caa26325426b558c35af4322f797a=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:c4140ad89752b59d3d1567f6c2c5922d4b1a3eb7ab3ac41472772fcc4a77a447=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:0ee208147b93c91fdda2ded4a5daf71c91d2a491e94b56e29c5edc11d16b5f11=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:98d989819241cb7170e0660f1be3f4e1a6a9e70ba0c4f5c4c7bded2b531e46b6=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:1b8c7e5752cc75d8be572ac609fc4e1e415387acac0e70b80922b8bfc82894f9=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:3e5e9c7e2b11707d02a8dde9f71dacb294c3bce820d013f817c57c52ee141570=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:a5588164e8323980b589e3f4a8ca819e50816ec140fcf8f66904ed539047b30b=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:7c694b533384ef3d60b9743a8e3d50a8d1586d5253d2a60d227aa5526966a54b=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:dfb98350c924a6ea7572103667da653b98c597bf22aadec19f27866c987457fa=docker://localhost:55000/library/alpine:3.20
docker://quay.io/podman/hello:latest=docker://localhost:55000/podman/hello:latest
docker://quay.io/podman/hello:latest@sha256:43de9874507eaa8ffd88eac885b672b1dfc57cc583d9ad920850f97f19809f8f=docker://localhost:55000/podman/hello:latest
docker://quay.io/podman/hello:latest@sha256:5c44ef36dc5e35a76904da0e028cf9413e0176a653525162368af13fed03571c=docker://localhost:55000/podman/hello:latest
docker://quay.io/podman/hello:latest@sha256:92be1897bfcf5a8c9e59ce538645d7c159253717fa8edbb964ff5a259a5535ab=docker://localhost:55000/podman/hello:latest
docker://quay.io/podman/hello:latest@sha256:6fba1b311cad0201c377469c66c9cdfc4c48ea99a186869c7ce2609e0a013965=docker://localhost:55000/podman/hello:latest
oci:///tmp/oci-test-image=docker://localhost:55000/tmp/oci-test-image:latest
oci:///tmp/oci-test-image@sha256:b5632fc886395e5595e9aed61f2e8473079fb81666f229ec5c3772d2ee64c389=docker://localhost:55000/tmp/oci-test-image:latest
oci:///tmp/oci-test-image@sha256:95db54aed932be984073d01b9d10b3e7b7673184328bcac26ea1f287d4d75da9=docker://localhost:55000/tmp/oci-test-image:latest
oci:///tmp/oci-test-image@sha256:223682a763ecb26ad463846e6112caf5b34630cca10cc67e8d044da28c760707=docker://localhost:55000/tmp/oci-test-image:latest
oci:///tmp/oci-test-image@sha256:2b54b7513fa93f65b168ce91c3658939b0169c87b675d902f7ccd1a8726db1f8=docker://localhost:55000/tmp/oci-test-image:latest
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:b87097994ed62fbf1de5b4c35b4c28e5b654a1cc5e87f0b8a187e5c1f8cba0a0=docker://localhost:55000/ubi9/ubi-minimal:sha256-b87097994ed62fbf1de5b4c35b4c28e5b654a1cc5e87f0b8a187e5c1f8cba0a0

Expected Outcome

All sub-digests of any index image in the mapping.txt are listed directly below it.

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.

@openshift-ci openshift-ci bot requested review from adolfo-ab and aguidirh February 18, 2026 20:07
@dorzel
Copy link
Member Author

dorzel commented Feb 18, 2026

/jira refresh

@openshift-ci-robot openshift-ci-robot added jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. and removed jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Feb 18, 2026
@openshift-ci-robot
Copy link

@dorzel: This pull request references Jira Issue OCPBUGS-66263, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state ASSIGNED, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @nidangavali

Details

In response to this:

/jira refresh

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.

@openshift-ci openshift-ci bot requested a review from nidangavali February 18, 2026 20:10
@dorzel dorzel force-pushed the OCPBUGS-66263 branch 2 times, most recently from ed935b0 to 054890d Compare February 18, 2026 20:25
@dorzel
Copy link
Member Author

dorzel commented Feb 18, 2026

/retest

Copy link
Contributor

@adolfo-ab adolfo-ab left a comment

Choose a reason for hiding this comment

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

Just a small suggestion related to the test code, otherwise lgtm.

@openshift-ci
Copy link

openshift-ci bot commented Feb 19, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

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

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

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

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

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between a63ea77 and a51c524.

📒 Files selected for processing (2)
  • internal/pkg/cli/dryrun.go
  • internal/pkg/cli/dryrun_test.go

Walkthrough

Dry-run now detects OCI manifest lists, expands them into per-instance digest source->destination mappings (including digest-pinned destinations), and records sub-digests for missing-image tracking. Tests add an OCI layout builder and verify sub-digest mapping and destination generation.

Changes

Cohort / File(s) Summary
Dry-run manifest list logic
internal/pkg/cli/dryrun.go
Added manifest-list detection and expansion inside DryRun: new getManifestListDigests(ctx, source) to extract instance digests, generation of per-instance source@digest -> destination mapping lines, tracking sub-digests for missing-image handling, and subDigestDestination to produce digest-pinned docker destinations. Added imports: fmt, strings, github.com/containers/image/v5/imgmanifest, github.com/containers/image/v5/transports/alltransports, plus related helpers.
Tests: OCI manifest list
internal/pkg/cli/dryrun_test.go
Added createTestOCILayout() helper to create a minimal OCI layout with an index/manifest list and three sub-manifests; added TestDryRunWithManifestList() to assert dry-run includes sub-digests; added TestSubDigestDestination() to validate destination generation for various transport formats. Extended test imports (encoding/json, github.com/opencontainers/go-digest, github.com/opencontainers/image-spec/specs-go/v1, test assertion helpers).

Sequence Diagram(s)

sequenceDiagram
    participant Executor as Dry-Run Executor
    participant Parser as alltransports.ParseImageName
    participant Transport as containers/image Source
    participant Manifest as Manifest Parser
    participant Mapper as Mapping Writer

    Executor->>Parser: Parse image name (source)
    Parser-->>Executor: reference (or fallback)
    Executor->>Transport: Open image source (system context)
    Transport->>Manifest: Fetch manifest bytes
    Manifest-->>Executor: manifest bytes
    alt manifest is a manifest list
        Executor->>Manifest: Parse index -> instance digests
        Manifest-->>Executor: [digest1, digest2, ...]
        loop for each digest
            Executor->>Mapper: write "source@digest = sub-destination"
            Mapper-->>Executor: mapping written
        end
    else single image
        Executor->>Mapper: write single mapping
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test Structure And Quality ⚠️ Warning TestDryRunWithManifestList creates a temporary directory using t.TempDir() but lacks the defer os.RemoveAll(testFolder) cleanup statement present in other test functions, breaking established codebase patterns. Add defer os.RemoveAll(testFolder) immediately after the testFolder := t.TempDir() line in TestDryRunWithManifestList to maintain consistency with cleanup patterns.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly references the Jira issue and accurately describes the main change: adding index image sub-digests to dry run mapping.txt output.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Stable And Deterministic Test Names ✅ Passed All test names are stable and deterministic with no dynamic values, timestamps, UUIDs, or generated identifiers.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@openshift-ci-robot
Copy link

@dorzel: This pull request references Jira Issue OCPBUGS-66263, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @nidangavali

Details

In response to this:

Description

This PR adds functionality to include index image sub-digests in mapping.txt, as part of the linked bug. This facilitates users who manually mirror based on mapping.txt or use automated security scanning which requires all digests to be listed.

Github / Jira issue: https://issues.redhat.com/browse/OCPBUGS-66263

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Code Improvements (Refactoring, Performance, CI upgrades, etc)
  • Internal repo assets (diagrams / docs on github repo)
  • This change requires a documentation update on openshift docs

How Has This Been Tested?

  • Ran unit test suite
  • Manually ran an m2d dry run and inspected the mapping.txt
kind: ImageSetConfiguration
apiVersion: mirror.openshift.io/v2alpha1
mirror:
 additionalImages:
   - name: registry.access.redhat.com/ubi9/ubi-minimal:9.4
   - name: docker.io/library/alpine:3.20
   - name: quay.io/podman/hello:latest
   - name: oci:///tmp/oci-test-image
   - name: registry.access.redhat.com/ubi9/ubi-minimal@sha256:b87097994ed62fbf1de5b4c35b4c28e5b654a1cc5e87f0b8a187e5c1f8cba0a0

Corresponding mapping.txt:

docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:f5d2c6a1e0c86e4234ea601552dbabb4ced0e013a1efcbfb439f1f6a7a9275b0=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:96f4394d39e6edb69ca51f000f3e7dfb62990f55868134cfd83c82177651e848=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:39c59a30e3ecae689c23b27e54a81e03d9a5db22d11890edaf6bb16bac783e8b=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:d187f310724694b1daae2f99f6f86ae05b573eed6826fa40d4233e76bd07312e=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://docker.io/library/alpine:3.20=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:b0cb30c51c47cdfde647364301758b14c335dea2fddc9490d4f007d67ecb2538=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:66f45812e9b5053abd384c1c6037261e526b1a0a4ac783ecaf3d5e04a94a0412=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:a4e2d43613b6ee71c96f9d26a2e515729fafeb75e4f0dfea524445b3344dccfe=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:4135f5ea9c06f0857b68f7a6beaa40480d1f11d64e6341d11ed061cce5f66214=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:1e161cda0d91d4d1c28464a134f1c0fef668a7c6e486017314e06a64b875c288=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:a1acb89d2fc6e0d5c1f3b3be4fd4bda3bd29ca8d5df1461b03aabefefa9d4321=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:9ace55212059e43b0b246bb975a26b4f6fb41f7384bc87b57ad3aae2a6e5e817=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:13688eb4bf30a8a94d920e2c6489aceadb6caa26325426b558c35af4322f797a=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:c4140ad89752b59d3d1567f6c2c5922d4b1a3eb7ab3ac41472772fcc4a77a447=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:0ee208147b93c91fdda2ded4a5daf71c91d2a491e94b56e29c5edc11d16b5f11=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:98d989819241cb7170e0660f1be3f4e1a6a9e70ba0c4f5c4c7bded2b531e46b6=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:1b8c7e5752cc75d8be572ac609fc4e1e415387acac0e70b80922b8bfc82894f9=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:3e5e9c7e2b11707d02a8dde9f71dacb294c3bce820d013f817c57c52ee141570=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:a5588164e8323980b589e3f4a8ca819e50816ec140fcf8f66904ed539047b30b=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:7c694b533384ef3d60b9743a8e3d50a8d1586d5253d2a60d227aa5526966a54b=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:dfb98350c924a6ea7572103667da653b98c597bf22aadec19f27866c987457fa=docker://localhost:55000/library/alpine:3.20
docker://quay.io/podman/hello:latest=docker://localhost:55000/podman/hello:latest
docker://quay.io/podman/hello:latest@sha256:43de9874507eaa8ffd88eac885b672b1dfc57cc583d9ad920850f97f19809f8f=docker://localhost:55000/podman/hello:latest
docker://quay.io/podman/hello:latest@sha256:5c44ef36dc5e35a76904da0e028cf9413e0176a653525162368af13fed03571c=docker://localhost:55000/podman/hello:latest
docker://quay.io/podman/hello:latest@sha256:92be1897bfcf5a8c9e59ce538645d7c159253717fa8edbb964ff5a259a5535ab=docker://localhost:55000/podman/hello:latest
docker://quay.io/podman/hello:latest@sha256:6fba1b311cad0201c377469c66c9cdfc4c48ea99a186869c7ce2609e0a013965=docker://localhost:55000/podman/hello:latest
oci:///tmp/oci-test-image=docker://localhost:55000/tmp/oci-test-image:latest
oci:///tmp/oci-test-image@sha256:b5632fc886395e5595e9aed61f2e8473079fb81666f229ec5c3772d2ee64c389=docker://localhost:55000/tmp/oci-test-image:latest
oci:///tmp/oci-test-image@sha256:95db54aed932be984073d01b9d10b3e7b7673184328bcac26ea1f287d4d75da9=docker://localhost:55000/tmp/oci-test-image:latest
oci:///tmp/oci-test-image@sha256:223682a763ecb26ad463846e6112caf5b34630cca10cc67e8d044da28c760707=docker://localhost:55000/tmp/oci-test-image:latest
oci:///tmp/oci-test-image@sha256:2b54b7513fa93f65b168ce91c3658939b0169c87b675d902f7ccd1a8726db1f8=docker://localhost:55000/tmp/oci-test-image:latest
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:b87097994ed62fbf1de5b4c35b4c28e5b654a1cc5e87f0b8a187e5c1f8cba0a0=docker://localhost:55000/ubi9/ubi-minimal:sha256-b87097994ed62fbf1de5b4c35b4c28e5b654a1cc5e87f0b8a187e5c1f8cba0a0

Expected Outcome

All sub-digests of any index image in the mapping.txt are listed directly below it.

Summary by CodeRabbit

  • New Features

  • Dry-run output now detects multi-architecture container images and includes individual sub-digest mappings in addition to the primary image mapping.

  • Tests

  • Added test coverage for manifest list detection and validation of sub-digest mappings during dry-run execution.

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.

Copy link

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

Caution

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

⚠️ Outside diff range comments (1)
internal/pkg/cli/dryrun_test.go (1)

189-190: ⚠️ Potential issue | 🟡 Minor

Pre-existing: assert.FileExists on line 190 checks mappingPath instead of missingImgsPath.

This appears to be a copy-paste bug in the existing test — it asserts existence of mappingPath (already checked on line 177) instead of the newly constructed missingImgsPath. Not introduced by this PR, but worth fixing while you're here.

Proposed fix
-		assert.FileExists(t, mappingPath)
+		assert.FileExists(t, missingImgsPath)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/pkg/cli/dryrun_test.go` around lines 189 - 190, The test currently
constructs missingImgsPath but then mistakenly calls assert.FileExists(t,
mappingPath) again; update the assertion to check the correct variable by
replacing the second assert.FileExists invocation to assert.FileExists(t,
missingImgsPath) so the test verifies the presence of the missing images file
(variable missingImgsPath) rather than re-checking mappingPath.
🧹 Nitpick comments (2)
internal/pkg/cli/dryrun_test.go (1)

295-296: Nit: os.RemoveAll is redundant for t.TempDir().

t.TempDir() already registers automatic cleanup when the test ends. The explicit defer os.RemoveAll(testFolder) is unnecessary. The same pattern also appears on line 48 and line 120 in the pre-existing tests.

Proposed fix
 		testFolder := t.TempDir()
-		defer os.RemoveAll(testFolder)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/pkg/cli/dryrun_test.go` around lines 295 - 296, The test creates
testFolder via t.TempDir() and then redundantly calls defer
os.RemoveAll(testFolder); remove the unnecessary explicit cleanup calls (the
defer os.RemoveAll(...) lines) so the tests rely on t.TempDir()'s automatic
cleanup—search for occurrences around testFolder/t.TempDir() and delete the
os.RemoveAll usages.
internal/pkg/cli/dryrun.go (1)

44-57: Performance: getManifestListDigests is called for every image, adding network I/O to the dry-run path.

For docker:// sources this opens a connection and fetches the manifest from a remote registry. In large image sets (hundreds/thousands of images), this could significantly degrade dry-run speed. Consider:

  1. Parallelizing the manifest lookups (e.g., bounded goroutine pool), or
  2. Adding a flag/option to opt into sub-digest expansion, or
  3. Caching the system context and reusing connections where possible.

Not a blocker since errors are gracefully handled and logged at debug level, but worth considering for large-scale usage.

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

In `@internal/pkg/cli/dryrun.go` around lines 44 - 57, The dry-run loop currently
calls getManifestListDigests for every image (in internal/pkg/cli/dryrun.go),
causing remote registry network I/O per image; change this by adding an opt-in
flag (e.g., --expand-subdigests) checked in the dry-run path and only call
getManifestListDigests when enabled, and/or replace the sequential calls with a
bounded goroutine worker pool that performs getManifestListDigests concurrently
for images (use a shared cached system context/reused HTTP client across workers
to avoid repeated connection setup), then collect results and produce the same
subSource -> destination writes to buff as before; ensure getManifestListDigests
is called from the worker pool and errors remain debug-logged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@internal/pkg/cli/dryrun_test.go`:
- Around line 189-190: The test currently constructs missingImgsPath but then
mistakenly calls assert.FileExists(t, mappingPath) again; update the assertion
to check the correct variable by replacing the second assert.FileExists
invocation to assert.FileExists(t, missingImgsPath) so the test verifies the
presence of the missing images file (variable missingImgsPath) rather than
re-checking mappingPath.

---

Nitpick comments:
In `@internal/pkg/cli/dryrun_test.go`:
- Around line 295-296: The test creates testFolder via t.TempDir() and then
redundantly calls defer os.RemoveAll(testFolder); remove the unnecessary
explicit cleanup calls (the defer os.RemoveAll(...) lines) so the tests rely on
t.TempDir()'s automatic cleanup—search for occurrences around
testFolder/t.TempDir() and delete the os.RemoveAll usages.

In `@internal/pkg/cli/dryrun.go`:
- Around line 44-57: The dry-run loop currently calls getManifestListDigests for
every image (in internal/pkg/cli/dryrun.go), causing remote registry network I/O
per image; change this by adding an opt-in flag (e.g., --expand-subdigests)
checked in the dry-run path and only call getManifestListDigests when enabled,
and/or replace the sequential calls with a bounded goroutine worker pool that
performs getManifestListDigests concurrently for images (use a shared cached
system context/reused HTTP client across workers to avoid repeated connection
setup), then collect results and produce the same subSource -> destination
writes to buff as before; ensure getManifestListDigests is called from the
worker pool and errors remain debug-logged.

ℹ️ Review info

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

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between b27c4b9 and 06bc475.

📒 Files selected for processing (2)
  • internal/pkg/cli/dryrun.go
  • internal/pkg/cli/dryrun_test.go

Copy link

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
internal/pkg/cli/dryrun.go (1)

59-68: ⚠️ Potential issue | 🟡 Minor

Sub-digest entries are not checked against the local cache

Sub-digest lines are now written to mapping.txt but are never checked via o.Mirror.Check and are never added to missingImgsBuff. In a partial-cache scenario (e.g. the manifest list index is present but one or more architecture blobs are absent), these missing entries will go unreported, and the warning counter on line 87 will undercount.

Consider extending the cache-check loop to cover the expanded sub-digest entries, or at a minimum add a comment explaining that sub-digest cache validation is intentionally deferred to the full mirror run.

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

In `@internal/pkg/cli/dryrun.go` around lines 59 - 68, The current loop only
checks img.Destination against the local cache (o.Mirror.Check) and therefore
ignores any expanded sub-digest entries written into mapping.txt; update the
logic in the dry-run path so that after expanding an image into sub-digest
destinations you iterate each sub-digest destination and call
o.Mirror.Check(ctx, subDest, o.Opts, false), and for any err or !exists write
the mapping (use img.Source or the appropriate sub-source mapping) into
missingImgsBuff and increment nbMissingImgs; if intentional deferral is required
instead, add a clear comment near o.Opts.IsMirrorToDisk() explaining that
sub-digest cache validation is deferred to the full mirror run and why.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/pkg/cli/dryrun.go`:
- Around line 45-57: Currently o.getManifestListDigests is called for every
img.Source during dry-run which triggers a full remote manifest fetch per image;
update the logic to avoid unnecessary network calls by only invoking
o.getManifestListDigests when the source is not already digest-pinned (i.e.,
img.Source does not contain a digest "@...") or when a new boolean flag on the
CopyImageSchema (e.g., IsManifestList) is unset — alternatively compute and set
IsManifestList during the collection phase where manifests are already fetched
and consult that flag here; also change the logger call from o.Log.Debug(...) to
o.Log.Warn(...) when getManifestListDigests returns an error so users see
missing sub-digests without debug logging.
- Around line 52-56: The current loop writing sub-digest mappings uses
sourceBase (from img.Source) but leaves img.Destination with its tag, causing
each arch-specific line to map to the same dest tag and overwrite; update the
loop that iterates manifestDigests (the block building subSource and calling
buff.WriteString) to compute a destBase that has any tag removed before
appending the sub-digest. Implement tag-stripping for img.Destination by
removing the last ":tag" occurrence (i.e., split at the last ':' after the final
'/') or, better, reuse the same image-reference parsing used for img.Source to
produce a destination reference without tag, then append "@"+digest so lines
become destBase+"@"+digest; use that destBase when writing buff.WriteString
instead of the original img.Destination.
- Around line 98-106: getManifestListDigests currently calls
alltransports.ParseImageName and fails for Cincinnati-style image names that
lack a transport prefix; update getManifestListDigests to detect this case and
retry parsing by prepending "docker://" when alltransports.ParseImageName
returns a transport-not-found/parse error (or when source does not contain
"://"), so that images like
"quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64" are parsed and
manifest-list expansion proceeds; keep the original error handling for other
parse failures and ensure the retry only happens when the input appears to lack
a transport prefix.

---

Outside diff comments:
In `@internal/pkg/cli/dryrun.go`:
- Around line 59-68: The current loop only checks img.Destination against the
local cache (o.Mirror.Check) and therefore ignores any expanded sub-digest
entries written into mapping.txt; update the logic in the dry-run path so that
after expanding an image into sub-digest destinations you iterate each
sub-digest destination and call o.Mirror.Check(ctx, subDest, o.Opts, false), and
for any err or !exists write the mapping (use img.Source or the appropriate
sub-source mapping) into missingImgsBuff and increment nbMissingImgs; if
intentional deferral is required instead, add a clear comment near
o.Opts.IsMirrorToDisk() explaining that sub-digest cache validation is deferred
to the full mirror run and why.

ℹ️ Review info

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

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 06bc475 and 1a213a7.

📒 Files selected for processing (2)
  • internal/pkg/cli/dryrun.go
  • internal/pkg/cli/dryrun_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/pkg/cli/dryrun_test.go

Comment on lines +45 to +57
// Try to get manifest list sub-digests from source
manifestDigests, err := o.getManifestListDigests(ctx, img.Source)
if err != nil {
o.Log.Debug("unable to get manifest list info for %s: %v", img.Source, err)
} else if len(manifestDigests) > 0 {
// This is a manifest list, write each sub-digest
// Remove the digest suffix (if any) to get the base reference
sourceBase, _, _ := strings.Cut(img.Source, "@")
for _, digest := range manifestDigests {
subSource := sourceBase + "@" + digest
buff.WriteString(subSource + "=" + img.Destination + "\n")
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

O(n) network calls per dry run degrades performance and may hit rate limits

getManifestListDigests opens a full network connection (TLS handshake, auth, manifest fetch) for every entry in allImages, unconditionally, on every dry run. GetManifest is noted in the library as a call that "may use a remote (= slow) service." For image sets with dozens or hundreds of entries this is a significant regression in dry run responsiveness, and sequential manifest pulls against rate-limited registries (e.g. registry.access.redhat.com, registry.redhat.io) can exhaust quotas mid-run.

Consider whether this lookup can be gated — e.g. only invoked when a tag-based source reference is present (a digest-pinned source is already expanded; no lookup needed), or whether the CopyImageSchema structs could carry a pre-computed IsManifestList flag set during the collection phase where the manifest is already fetched.

Additionally, when getManifestListDigests returns an error the failure is logged only at Debug level (line 48). For an actual connectivity or auth problem against a real manifest-list image, users running without debug logging will see a silently truncated mapping.txt with no indication that sub-digests are missing. Elevating to Warn would be more appropriate.

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

In `@internal/pkg/cli/dryrun.go` around lines 45 - 57, Currently
o.getManifestListDigests is called for every img.Source during dry-run which
triggers a full remote manifest fetch per image; update the logic to avoid
unnecessary network calls by only invoking o.getManifestListDigests when the
source is not already digest-pinned (i.e., img.Source does not contain a digest
"@...") or when a new boolean flag on the CopyImageSchema (e.g., IsManifestList)
is unset — alternatively compute and set IsManifestList during the collection
phase where manifests are already fetched and consult that flag here; also
change the logger call from o.Log.Debug(...) to o.Log.Warn(...) when
getManifestListDigests returns an error so users see missing sub-digests without
debug logging.

Copy link
Contributor

@aguidirh aguidirh left a comment

Choose a reason for hiding this comment

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

Thanks @dorzel, I've added some comments, I'm wondering if we should include the child digests of the manifest list only when the customer asks for it (maybe a flag where they can select the archs or all) ?

I would like to discuss this option with the team (@r4f4 and @adolfo-ab) to avoid breaking changes. I know some customers currently are depending on the current mappings.txt to copy the images with skopeo.

Copy link

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

Caution

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

⚠️ Outside diff range comments (1)
internal/pkg/cli/dryrun.go (1)

73-100: ⚠️ Potential issue | 🟡 Minor

Use a consistent total when reporting missing images.

nbMissingImgs is incremented for sub-digest entries (Line 77), but the denominator remains len(allImages) (Line 99). This can produce misleading ratios (e.g., 8/3).

Proposed fix
-	nbMissingImgs := 0
+	nbMissingImgs := 0
+	totalMappings := 0
@@
 	for _, img := range allImages {
 		buff.WriteString(img.Source + "=" + img.Destination + "\n")
+		totalMappings++
@@
 			for _, digest := range manifestDigests {
 				subSource := sourceBase + "@" + digest
 				subDest := subDigestDestination(img.Destination, digest)
 				buff.WriteString(subSource + "=" + subDest + "\n")
+				totalMappings++
 				subDigestEntries = append(subDigestEntries, subDigestEntry{source: subSource, dest: subDest})
 			}
 		}
@@
-		o.Log.Warn(emoji.Warning+"  %d/%d images necessary for mirroring are not available in the cache.", nbMissingImgs, len(allImages))
+		o.Log.Warn(emoji.Warning+"  %d/%d images necessary for mirroring are not available in the cache.", nbMissingImgs, totalMappings)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/pkg/cli/dryrun.go` around lines 73 - 100, The warning uses
nbMissingImgs for the numerator but len(allImages) for the denominator which is
inconsistent because nbMissingImgs is incremented for sub-digest entries; fix by
computing a consistent total (e.g., totalExpectedImgs) before writing warnings:
initialize totalExpectedImgs := len(allImages), when iterating subDigestEntries
add len(subDigestEntries) (or increment totalExpectedImgs in the same loop where
nbMissingImgs is incremented), then use totalExpectedImgs as the denominator in
o.Log.Warn instead of len(allImages); reference nbMissingImgs, subDigestEntries,
allImages and the two o.Log.Warn calls to update the message.
♻️ Duplicate comments (1)
internal/pkg/cli/dryrun.go (1)

51-55: ⚠️ Potential issue | 🟠 Major

Avoid unconditional manifest lookups in the dry-run image loop.

ExecutorSchema.DryRun still calls ExecutorSchema.getManifestListDigests for every image entry. That introduces one remote manifest fetch per image and can significantly slow dry-runs or hit registry limits at scale.

#!/bin/bash
# Verify unconditional invocation of getManifestListDigests inside the per-image loop.
cat -n internal/pkg/cli/dryrun.go | sed -n '44,70p'

# Confirm call site(s) and surrounding guards.
rg -nP --type=go -C3 'getManifestListDigests\(ctx,\s*img\.Source\)' internal/pkg/cli/dryrun.go
🧹 Nitpick comments (1)
internal/pkg/cli/dryrun_test.go (1)

314-321: Fail fast on registry setup errors in this test path.

Using t.Errorf during test bootstrap lets execution continue after setup failure, which can obscure the real failure cause.

Proposed fix
 		regCfg, err := setupRegForTest(testFolder)
 		if err != nil {
-			t.Errorf("storage cache error: %v ", err)
+			t.Fatalf("storage cache error: %v", err)
 		}
 		reg, err := registry.NewRegistry(context.Background(), regCfg)
 		if err != nil {
-			t.Errorf("storage cache error: %v ", err)
+			t.Fatalf("storage cache error: %v", err)
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/pkg/cli/dryrun_test.go` around lines 314 - 321, The test currently
uses t.Errorf when setupRegForTest(testFolder) or registry.NewRegistry(...)
fails, which allows the test to continue; change both t.Errorf calls to t.Fatalf
(or call t.FailNow) so the test aborts immediately on setup failure, and include
context in the message referencing setupRegForTest/regCfg and
registry.NewRegistry/reg to make the failure point clear.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@internal/pkg/cli/dryrun.go`:
- Around line 73-100: The warning uses nbMissingImgs for the numerator but
len(allImages) for the denominator which is inconsistent because nbMissingImgs
is incremented for sub-digest entries; fix by computing a consistent total
(e.g., totalExpectedImgs) before writing warnings: initialize totalExpectedImgs
:= len(allImages), when iterating subDigestEntries add len(subDigestEntries) (or
increment totalExpectedImgs in the same loop where nbMissingImgs is
incremented), then use totalExpectedImgs as the denominator in o.Log.Warn
instead of len(allImages); reference nbMissingImgs, subDigestEntries, allImages
and the two o.Log.Warn calls to update the message.

---

Nitpick comments:
In `@internal/pkg/cli/dryrun_test.go`:
- Around line 314-321: The test currently uses t.Errorf when
setupRegForTest(testFolder) or registry.NewRegistry(...) fails, which allows the
test to continue; change both t.Errorf calls to t.Fatalf (or call t.FailNow) so
the test aborts immediately on setup failure, and include context in the message
referencing setupRegForTest/regCfg and registry.NewRegistry/reg to make the
failure point clear.

ℹ️ Review info

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

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 1a213a7 and a63ea77.

📒 Files selected for processing (2)
  • internal/pkg/cli/dryrun.go
  • internal/pkg/cli/dryrun_test.go

@openshift-ci-robot
Copy link

@dorzel: An error was encountered querying GitHub for users with public email (ngavali@redhat.com) for bug OCPBUGS-66263 on the Jira server at https://issues.redhat.com/. No known errors were detected, please see the full error message for details.

Full error message. Post "http://ghproxy/graphql": dial tcp 172.30.229.2:80: connect: connection refused

Please contact an administrator to resolve this issue, then request a bug refresh with /jira refresh.

Details

In response to this:

Description

This PR adds functionality to include index image sub-digests in mapping.txt, as part of the linked bug. This facilitates users who manually mirror based on mapping.txt or use automated security scanning which requires all digests to be listed.

Github / Jira issue: https://issues.redhat.com/browse/OCPBUGS-66263

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Code Improvements (Refactoring, Performance, CI upgrades, etc)
  • Internal repo assets (diagrams / docs on github repo)
  • This change requires a documentation update on openshift docs

How Has This Been Tested?

  • Ran unit test suite
  • Manually ran an m2d dry run and inspected the mapping.txt
kind: ImageSetConfiguration
apiVersion: mirror.openshift.io/v2alpha1
mirror:
 additionalImages:
   - name: registry.access.redhat.com/ubi9/ubi-minimal:9.4
   - name: docker.io/library/alpine:3.20
   - name: quay.io/podman/hello:latest
   - name: oci:///tmp/oci-test-image
   - name: registry.access.redhat.com/ubi9/ubi-minimal@sha256:c0e70387664f30cd9cf2795b547e4a9a51002c44a4a86aa9335ab030134bf392

Corresponding mapping.txt:

docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4=docker://localhost:55000/ubi9/ubi-minimal:9.4
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:f5d2c6a1e0c86e4234ea601552dbabb4ced0e013a1efcbfb439f1f6a7a9275b0=docker://localhost:55000/ubi9/ubi-minimal@sha256:f5d2c6a1e0c86e4234ea601552dbabb4ced0e013a1efcbfb439f1f6a7a9275b0
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:96f4394d39e6edb69ca51f000f3e7dfb62990f55868134cfd83c82177651e848=docker://localhost:55000/ubi9/ubi-minimal@sha256:96f4394d39e6edb69ca51f000f3e7dfb62990f55868134cfd83c82177651e848
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:39c59a30e3ecae689c23b27e54a81e03d9a5db22d11890edaf6bb16bac783e8b=docker://localhost:55000/ubi9/ubi-minimal@sha256:39c59a30e3ecae689c23b27e54a81e03d9a5db22d11890edaf6bb16bac783e8b
docker://registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:d187f310724694b1daae2f99f6f86ae05b573eed6826fa40d4233e76bd07312e=docker://localhost:55000/ubi9/ubi-minimal@sha256:d187f310724694b1daae2f99f6f86ae05b573eed6826fa40d4233e76bd07312e
docker://docker.io/library/alpine:3.20=docker://localhost:55000/library/alpine:3.20
docker://docker.io/library/alpine:3.20@sha256:b0cb30c51c47cdfde647364301758b14c335dea2fddc9490d4f007d67ecb2538=docker://localhost:55000/library/alpine@sha256:b0cb30c51c47cdfde647364301758b14c335dea2fddc9490d4f007d67ecb2538
docker://docker.io/library/alpine:3.20@sha256:66f45812e9b5053abd384c1c6037261e526b1a0a4ac783ecaf3d5e04a94a0412=docker://localhost:55000/library/alpine@sha256:66f45812e9b5053abd384c1c6037261e526b1a0a4ac783ecaf3d5e04a94a0412
docker://docker.io/library/alpine:3.20@sha256:a4e2d43613b6ee71c96f9d26a2e515729fafeb75e4f0dfea524445b3344dccfe=docker://localhost:55000/library/alpine@sha256:a4e2d43613b6ee71c96f9d26a2e515729fafeb75e4f0dfea524445b3344dccfe
docker://docker.io/library/alpine:3.20@sha256:4135f5ea9c06f0857b68f7a6beaa40480d1f11d64e6341d11ed061cce5f66214=docker://localhost:55000/library/alpine@sha256:4135f5ea9c06f0857b68f7a6beaa40480d1f11d64e6341d11ed061cce5f66214
docker://docker.io/library/alpine:3.20@sha256:1e161cda0d91d4d1c28464a134f1c0fef668a7c6e486017314e06a64b875c288=docker://localhost:55000/library/alpine@sha256:1e161cda0d91d4d1c28464a134f1c0fef668a7c6e486017314e06a64b875c288
docker://docker.io/library/alpine:3.20@sha256:a1acb89d2fc6e0d5c1f3b3be4fd4bda3bd29ca8d5df1461b03aabefefa9d4321=docker://localhost:55000/library/alpine@sha256:a1acb89d2fc6e0d5c1f3b3be4fd4bda3bd29ca8d5df1461b03aabefefa9d4321
docker://docker.io/library/alpine:3.20@sha256:9ace55212059e43b0b246bb975a26b4f6fb41f7384bc87b57ad3aae2a6e5e817=docker://localhost:55000/library/alpine@sha256:9ace55212059e43b0b246bb975a26b4f6fb41f7384bc87b57ad3aae2a6e5e817
docker://docker.io/library/alpine:3.20@sha256:13688eb4bf30a8a94d920e2c6489aceadb6caa26325426b558c35af4322f797a=docker://localhost:55000/library/alpine@sha256:13688eb4bf30a8a94d920e2c6489aceadb6caa26325426b558c35af4322f797a
docker://docker.io/library/alpine:3.20@sha256:c4140ad89752b59d3d1567f6c2c5922d4b1a3eb7ab3ac41472772fcc4a77a447=docker://localhost:55000/library/alpine@sha256:c4140ad89752b59d3d1567f6c2c5922d4b1a3eb7ab3ac41472772fcc4a77a447
docker://docker.io/library/alpine:3.20@sha256:0ee208147b93c91fdda2ded4a5daf71c91d2a491e94b56e29c5edc11d16b5f11=docker://localhost:55000/library/alpine@sha256:0ee208147b93c91fdda2ded4a5daf71c91d2a491e94b56e29c5edc11d16b5f11
docker://docker.io/library/alpine:3.20@sha256:98d989819241cb7170e0660f1be3f4e1a6a9e70ba0c4f5c4c7bded2b531e46b6=docker://localhost:55000/library/alpine@sha256:98d989819241cb7170e0660f1be3f4e1a6a9e70ba0c4f5c4c7bded2b531e46b6
docker://docker.io/library/alpine:3.20@sha256:1b8c7e5752cc75d8be572ac609fc4e1e415387acac0e70b80922b8bfc82894f9=docker://localhost:55000/library/alpine@sha256:1b8c7e5752cc75d8be572ac609fc4e1e415387acac0e70b80922b8bfc82894f9
docker://docker.io/library/alpine:3.20@sha256:3e5e9c7e2b11707d02a8dde9f71dacb294c3bce820d013f817c57c52ee141570=docker://localhost:55000/library/alpine@sha256:3e5e9c7e2b11707d02a8dde9f71dacb294c3bce820d013f817c57c52ee141570
docker://docker.io/library/alpine:3.20@sha256:a5588164e8323980b589e3f4a8ca819e50816ec140fcf8f66904ed539047b30b=docker://localhost:55000/library/alpine@sha256:a5588164e8323980b589e3f4a8ca819e50816ec140fcf8f66904ed539047b30b
docker://docker.io/library/alpine:3.20@sha256:7c694b533384ef3d60b9743a8e3d50a8d1586d5253d2a60d227aa5526966a54b=docker://localhost:55000/library/alpine@sha256:7c694b533384ef3d60b9743a8e3d50a8d1586d5253d2a60d227aa5526966a54b
docker://docker.io/library/alpine:3.20@sha256:dfb98350c924a6ea7572103667da653b98c597bf22aadec19f27866c987457fa=docker://localhost:55000/library/alpine@sha256:dfb98350c924a6ea7572103667da653b98c597bf22aadec19f27866c987457fa
docker://quay.io/podman/hello:latest=docker://localhost:55000/podman/hello:latest
docker://quay.io/podman/hello:latest@sha256:43de9874507eaa8ffd88eac885b672b1dfc57cc583d9ad920850f97f19809f8f=docker://localhost:55000/podman/hello@sha256:43de9874507eaa8ffd88eac885b672b1dfc57cc583d9ad920850f97f19809f8f
docker://quay.io/podman/hello:latest@sha256:5c44ef36dc5e35a76904da0e028cf9413e0176a653525162368af13fed03571c=docker://localhost:55000/podman/hello@sha256:5c44ef36dc5e35a76904da0e028cf9413e0176a653525162368af13fed03571c
docker://quay.io/podman/hello:latest@sha256:92be1897bfcf5a8c9e59ce538645d7c159253717fa8edbb964ff5a259a5535ab=docker://localhost:55000/podman/hello@sha256:92be1897bfcf5a8c9e59ce538645d7c159253717fa8edbb964ff5a259a5535ab
docker://quay.io/podman/hello:latest@sha256:6fba1b311cad0201c377469c66c9cdfc4c48ea99a186869c7ce2609e0a013965=docker://localhost:55000/podman/hello@sha256:6fba1b311cad0201c377469c66c9cdfc4c48ea99a186869c7ce2609e0a013965
oci:///tmp/oci-test-image=docker://localhost:55000/tmp/oci-test-image:latest
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:c0e70387664f30cd9cf2795b547e4a9a51002c44a4a86aa9335ab030134bf392=docker://localhost:55000/ubi9/ubi-minimal:sha256-c0e70387664f30cd9cf2795b547e4a9a51002c44a4a86aa9335ab030134bf392
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:f5d2c6a1e0c86e4234ea601552dbabb4ced0e013a1efcbfb439f1f6a7a9275b0=docker://localhost:55000/ubi9/ubi-minimal@sha256:f5d2c6a1e0c86e4234ea601552dbabb4ced0e013a1efcbfb439f1f6a7a9275b0
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:96f4394d39e6edb69ca51f000f3e7dfb62990f55868134cfd83c82177651e848=docker://localhost:55000/ubi9/ubi-minimal@sha256:96f4394d39e6edb69ca51f000f3e7dfb62990f55868134cfd83c82177651e848
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:39c59a30e3ecae689c23b27e54a81e03d9a5db22d11890edaf6bb16bac783e8b=docker://localhost:55000/ubi9/ubi-minimal@sha256:39c59a30e3ecae689c23b27e54a81e03d9a5db22d11890edaf6bb16bac783e8b
docker://registry.access.redhat.com/ubi9/ubi-minimal@sha256:d187f310724694b1daae2f99f6f86ae05b573eed6826fa40d4233e76bd07312e=docker://localhost:55000/ubi9/ubi-minimal@sha256:d187f310724694b1daae2f99f6f86ae05b573eed6826fa40d4233e76bd07312e

Expected Outcome

All sub-digests of any index image in the mapping.txt are listed directly below it.

Summary by CodeRabbit

  • New Features

  • Dry-run now detects multi-architecture (manifest list) images and emits per-variant sub-digest source→destination mappings; docker destinations are also generated as digest-pinned targets and included in missing-image reporting.

  • Tests

  • Added tests that construct an OCI layout with a manifest list and verify dry-run includes expected per-sub-digest mappings and destination generation.

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.

@openshift-ci
Copy link

openshift-ci bot commented Mar 2, 2026

@dorzel: 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

jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. 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.

4 participants