Skip to content

fix(sync-ci-images): stop assuming oc image mirror preserves manifest digests#3196

Merged
ashwindasr merged 1 commit into
openshift-eng:mainfrom
fgallott:fix-sync-ci-images-digest
Jul 23, 2026
Merged

fix(sync-ci-images): stop assuming oc image mirror preserves manifest digests#3196
ashwindasr merged 1 commit into
openshift-eng:mainfrom
fgallott:fix-sync-ci-images-digest

Conversation

@fgallott

@fgallott fgallott commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Reverts the source_digest optimization from PR sync-ci-images: fix --stream handling, add --images filter #3093 that assumed oc image mirror preserves manifest digests across registries
  • When manifest format conversion occurs (OCI↔Docker v2s2), the digest changes, so the source digest cannot be used as the QCI digest
  • Restores the original behavior of always querying QCI for the actual digest after mirroring

Root cause

PR #3093 added an optimization: extract the digest from Konflux @sha256: source pullspecs and reuse it as the QCI digest for GC-prevention tagging, skipping the get_image_digest() call. This fails for openshift-base-nodejs because oc image mirror converts the manifest format, producing a different digest at QCI.

Impact: sync-ci-images job has been failing for ALL OCP versions since July 14 (~71 consecutive failures, builds #629#699). All CI base images including base-rhel9 are stale (last updated June 8), blocking local container builds for downstream teams (e.g., ironic team).

Test plan

  • uv run pytest doozer/tests/cli/test_images_streams.py — 28 passed
  • Trigger sync-ci-images Jenkins job for a single version to verify fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved image mirroring reliability by always refreshing the image digest from the floating tag after the mirror operation.
    • Updated ARM64 mirroring to follow the same refreshed digest behavior.
    • Removed prior digest reuse logic that could lead to stale image references.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: 3f695cd3-af27-4fc5-865e-5bda743fb53a

📥 Commits

Reviewing files that changed from the base of the PR and between 6b8baa5 and 1131216.

📒 Files selected for processing (1)
  • doozer/doozerlib/cli/images_streams.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • doozer/doozerlib/cli/images_streams.py

Walkthrough

Image stream mirroring removes the optional source digest override and always re-queries the QCI floating tag after oc image mirror. Main and ARM64 mirroring paths use the updated helper behavior.

Changes

Image mirroring digest handling

Layer / File(s) Summary
Refresh QCI digest after mirroring
doozer/doozerlib/cli/images_streams.py
The mirror_image helper no longer accepts source_digest; QCI digest selection always queries the floating tag after mirroring, and mirroring call sites use the updated contract.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 9 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Ai-Attribution ⚠️ Warning AI use is explicit, but the commit only has a Co-Authored-By: Claude trailer and no Assisted-by/Generated-by Red Hat attribution. Replace the AI co-author line with a Red Hat Assisted-by or Generated-by trailer, and keep any human co-authors separate.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: reverting the digest-preservation assumption in sync-ci-images.
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.
No-Weak-Crypto ✅ Passed No weak crypto was added or modified; the PR only reverts digest reuse, and existing md5 uses in the file are unchanged content hashes.
Container-Privileges ✅ Passed Only doozer/doozerlib/cli/images_streams.py changed; no K8s/manifests or privilege-related fields (privileged, hostPID, allowPrivilegeEscalation, SYS_ADMIN, root) were added.
No-Sensitive-Data-In-Logs ✅ Passed The patch only changes digest lookup logic/comments; no new logging of secrets, PII, tokens, or hostnames was introduced.
No-Hardcoded-Secrets ✅ Passed No hardcoded secrets, embedded credentials, or secret-like string literals appear in the changed code; it only handles digests and registry paths.
No-Injection-Vectors ✅ Passed Diff only changes digest re-query logic; no shell=True/eval/exec/pickle/yaml.load/os.system/dangerouslySetInnerHTML or SQL concatenation appears in the touched file.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@doozer/doozerlib/cli/images_streams.py`:
- Around line 209-212: Update the QCI digest refresh around get_image_digest so
a None result triggers a retry; if the second lookup still returns None, raise
an error instead of continuing with the previous digest. Preserve the existing
successful digest flow and ensure the failure is surfaced to the caller.
- Around line 209-212: Keep the dry_run path side-effect free by preventing the
post-mirror processing around get_image_digest from executing when dry_run is
enabled. Return immediately after the dry-run print in the image mirroring flow,
or guard the digest lookup and subsequent GC mirror and imagestream patch
operations with a not-dry-run condition; preserve the existing behavior for real
runs.
🪄 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-eng/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9569a86a-566c-44a0-b1aa-d7260afe4dbc

📥 Commits

Reviewing files that changed from the base of the PR and between d3df44b and 6b8baa5.

📒 Files selected for processing (1)
  • doozer/doozerlib/cli/images_streams.py

Comment on lines +209 to +212
# Always query the actual digest at QCI after mirroring.
# oc image mirror may convert manifest formats (OCI↔Docker v2s2),
# which changes the digest, so the source digest cannot be trusted.
qci_digest = get_image_digest(floating_qci_dest, registry_config_file)

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not silently continue when the QCI digest refresh fails.

get_image_digest can return None, after which the current warning-only path leaves the imagestream on its previous digest while reporting no failure. Retry the lookup and raise if it still cannot be confirmed.

🤖 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 `@doozer/doozerlib/cli/images_streams.py` around lines 209 - 212, Update the
QCI digest refresh around get_image_digest so a None result triggers a retry; if
the second lookup still returns None, raise an error instead of continuing with
the previous digest. Preserve the existing successful digest flow and ensure the
failure is surfaced to the caller.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep dry-run side-effect free.

The dry_run branch at Lines 191-193 only prints, so this unconditional lookup can fall through to the real GC mirror and imagestream patch. Return before querying, or guard the entire post-mirror block with if not dry_run.

Proposed fix
-                qci_digest = get_image_digest(floating_qci_dest, registry_config_file)
+                if dry_run:
+                    return
+                qci_digest = get_image_digest(floating_qci_dest, registry_config_file)
📝 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
# Always query the actual digest at QCI after mirroring.
# oc image mirror may convert manifest formats (OCI↔Docker v2s2),
# which changes the digest, so the source digest cannot be trusted.
qci_digest = get_image_digest(floating_qci_dest, registry_config_file)
# Always query the actual digest at QCI after mirroring.
# oc image mirror may convert manifest formats (OCI↔Docker v2s2),
# which changes the digest, so the source digest cannot be trusted.
if dry_run:
return
qci_digest = get_image_digest(floating_qci_dest, registry_config_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 `@doozer/doozerlib/cli/images_streams.py` around lines 209 - 212, Keep the
dry_run path side-effect free by preventing the post-mirror processing around
get_image_digest from executing when dry_run is enabled. Return immediately
after the dry-run print in the image mirroring flow, or guard the digest lookup
and subsequent GC mirror and imagestream patch operations with a not-dry-run
condition; preserve the existing behavior for real runs.

… digests

The source_digest optimization from PR openshift-eng#3093 assumed that oc image mirror
preserves manifest bytes across registries, so the source digest could be
reused as the QCI digest for GC-prevention tagging. This assumption is
wrong when manifest format conversion occurs (OCI↔Docker v2s2), which
changes the digest.

This broke sync-ci-images for ALL OCP versions since July 14 (~71
consecutive failures). The openshift-base-nodejs Konflux image triggers
the bug because its @sha256: pullspec sets source_digest, but the actual
digest at QCI differs after mirroring.

Revert to always querying QCI for the actual digest after mirroring,
which was the working behavior before PR openshift-eng#3093.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
@fgallott
fgallott force-pushed the fix-sync-ci-images-digest branch from 6b8baa5 to 1131216 Compare July 23, 2026 13:43
@openshift-ci

openshift-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

@fgallott: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/security 1131216 link false /test security

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.

@fgallott

Copy link
Copy Markdown
Contributor Author

/hold

@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 23, 2026

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

/lgtm
/approve

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Jul 23, 2026
@openshift-ci

openshift-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: rayfordj

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 Jul 23, 2026
@ashwindasr

Copy link
Copy Markdown
Contributor

/unhold

@openshift-ci openshift-ci Bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 23, 2026
@ashwindasr
ashwindasr merged commit 666e957 into openshift-eng:main Jul 23, 2026
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants