Skip to content

Fix platform publishing by preserving child manifest digests - #412

Merged
cooktheryan merged 3 commits into
mainfrom
fix/preserve-published-platform-digests
Oct 8, 2026
Merged

cooktheryan merged 3 commits into
mainfrom
fix/preserve-published-platform-digests

Conversation

@cooktheryan

@cooktheryan cooktheryan commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

The main-branch image builds and workload validation pass, but all three platform publishers fail their digest checks. podman manifest push --all recopies already-published child images and can recompress their layers, changing the digests pinned in the index.

Publish only the index with --all=false, referencing child manifests explicitly staged and preflighted by immutable digest in the destination repository. Keep legacy architecture repositories available as compatibility aliases. Keep exact platform/digest verification enabled. Add a native local-registry regression that forces different gzip levels for architecture publication and any attempted child recopy, then pulls both platforms. It also simulates a missing destination child and verifies no public index is created.

Validation: shell syntax and diff checks pass locally. The Platform publishing regression test runs the native Podman/local-registry test in GitHub Actions; production publishing remains gated on main after merge.

Failure: https://github.com/containers/fetchit/actions/runs/37838086184/job/113524870317

Summary by Sourcery

Publish multi-platform indexes without recopied child manifests changing their pinned digests.

Bug Fixes:

  • Preserve published child manifest digests when publishing multi-platform image indexes.

Enhancements:

  • Retain legacy architecture repositories as compatibility aliases while staging platform manifests in the destination repository.
  • Validate destination child manifests by immutable digest before publishing and publish only the verified index.
  • Verify both platforms remain pullable after publication and prevent index creation when a child manifest is unavailable.

Tests:

  • Add a local-registry regression test covering layer recompression, platform pulls, and missing-child preflight failures.

Signed-off-by: Ryan Cook <rcook@redhat.com>
@sourcery-ai

sourcery-ai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes platform publishing by pushing only the index, preserving the digests of child manifests already present in the destination repository. It adds a native local-registry regression that forces conflicting compression settings and validates successful pulls for both architectures.

Sequence diagram for preserving child manifest digests during platform publishing

sequenceDiagram
    participant Publisher
    participant Podman
    participant Registry
    participant Verification

    Publisher->>Podman: manifest create
    Publisher->>Podman: manifest add amd_digest
    Publisher->>Podman: manifest add arm_digest
    Publisher->>Podman: manifest push --all=false
    Podman->>Registry: Publish index only
    Registry-->>Podman: Preserve existing child manifests
    Publisher->>Podman: manifest inspect
    Podman->>Registry: Read published index
    Registry-->>Verification: Return index
    Verification->>Verification: Compare platform digests
Loading

Flow diagram for the platform publishing regression test

flowchart LR
    A[Publish architecture images with gzip setting A] --> R[Local registry]
    B[Publish architecture images with gzip setting B] --> R
    R --> I[Publish platform index only]
    I --> V[Verify exact child manifest digests]
    V --> P[Pull both platforms]
Loading

File-Level Changes

Change Details Files
Publish only the multi-architecture index while retaining strict child digest verification.
  • Switch manifest publication from --all to --all=false so existing child manifests are referenced rather than recopied.
  • Keep the post-publish index inspection and exact platform/digest checks unchanged.
scripts/publish-platform-index.sh
Add a regression test that detects child-manifest recompression during platform publishing.
  • Wrap Podman to use different gzip levels for architecture image pushes and manifest pushes.
  • Publish through the local registry and pull both platform variants to verify they remain usable and architecture-correct.
tests/integration/publishing/test-platform-publish.sh

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Signed-off-by: Ryan Cook <rcook@redhat.com>

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

Hey - I've found 13 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="scripts/publish-platform-index.sh" line_range="49" />
<code_context>
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
 python3 - "$scratch/index.json" "$amd_digest" "$arm_digest" <<'PYTHON'
</code_context>
<issue_to_address>
**Platform pulls fail after publishing**

When child manifests are pushed only to the architecture-specific repositories and are absent from the destination repository, `podman manifest push --all=false` publishes only the index to the destination, while the child manifests remain in the sibling repositories. `manifest inspect` checks index descriptors, not child availability, so publishing succeeds but clients cannot fetch either platform.

Ensure both child manifests are available in the destination repository before publishing the index, and verify their availability there.

Also at `scripts/publish-platform-index.sh:50`.
</issue_to_address>

### Comment 2
<location path="scripts/publish-platform-index.sh" line_range="49" />
<code_context>
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
 python3 - "$scratch/index.json" "$amd_digest" "$arm_digest" <<'PYTHON'
</code_context>
<issue_to_address>
**issue (review_instructions):** Index-only publishing can create a broken destination if either child manifest is missing or differs from the pinned digest.

The existing-repository assumption is not verified before publishing. Add a preflight check that fetches both child manifests from the destination repository and compares their immutable digests, failing closed if either is absent or mismatched; otherwise retain `--all` as a fallback when the children are not already present. For example: `podman manifest inspect "${options[@]}" "docker://$repository@$amd_digest" >/dev/null || { echo "missing AMD child manifest" >&2; exit 1; }` (and likewise for ARM). This prevents publishing an index that references unavailable or unexpected content.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Use comments that enhance the reliability and stability of the code by recommending best practices, and provide a safer alternative for potential security or integrity risks.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=77cf81e0-aaed-48cb-bc7b-43cb0ecdb17b&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 3
<location path="scripts/publish-platform-index.sh" line_range="49" />
<code_context>
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
 python3 - "$scratch/index.json" "$amd_digest" "$arm_digest" <<'PYTHON'
</code_context>
<issue_to_address>
**suggestion (review_instructions):** Add an integration test covering index-only publishing and digest preservation.

Please test the edge case where the referenced child manifests already exist with pinned digests, verifying that this command does not repush or recompress them. Also cover a failed or missing child-manifest reference to ensure registry verification fails rather than publishing an incorrect index.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Include comments that suggest adding tests for edge cases or invalid inputs to improve code robustness.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=99de523b-cf4c-499c-848e-2830fb0292eb&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 4
<location path="scripts/publish-platform-index.sh" line_range="49" />
<code_context>
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
 python3 - "$scratch/index.json" "$amd_digest" "$arm_digest" <<'PYTHON'
</code_context>
<issue_to_address>
**suggestion (review_instructions):** Omit the redundant `--all=false` flag to simplify the publish command while retaining index-only behavior.

`--all` defaults to false for `podman manifest push`, so this is equivalent and avoids an unnecessary explicit option:

```sh
podman manifest push "${options[@]}" --format=docker "$manifest" "docker://$destination"
```

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Verify that the suggestion simplifies the code logic or enhances performance.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=00e6741a-525d-4304-9e65-8fbc8e563bae&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 5
<location path="scripts/publish-platform-index.sh" line_range="49" />
<code_context>
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
 python3 - "$scratch/index.json" "$amd_digest" "$arm_digest" <<'PYTHON'
</code_context>
<issue_to_address>
**issue (review_instructions):** Pushing only the index can create a dangling multi-architecture manifest when child digests are not already present at the destination.

This assumes that `$amd_digest` and `$arm_digest` exist in the repository addressed by `$destination`. If `repository` and `destination` differ, or if either child manifest has not been copied there, `--all=false` publishes an index referencing unavailable manifests; clients pulling the destination tag will then fail to resolve the AMD or ARM image. The script should verify that the child manifests exist at the destination or retain `--all` when copying across repositories.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Include comments that identify potential bugs or risks in the code and clearly explain why the code is problematic, using specific examples.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=8a7aba75-59c7-4fb6-99cc-848bb5f45de2&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 6
<location path="scripts/publish-platform-index.sh" line_range="48" />
<code_context>
-podman manifest push --all "${options[@]}" --format=docker "$manifest" "docker://$destination"
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
</code_context>
<issue_to_address>
**suggestion (review_instructions):** Add a concrete example showing the intended index-only push behavior.

For example, rephrase this as: “Push only the manifest index with `--all=false`; do not copy the child manifests or blobs, whose pinned digests must remain unchanged.” This makes the suggested behavior explicit and actionable.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Check that the comment includes a specific example or rephrasing to illustrate the suggestion.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=4ea295bd-cb2d-4d56-9aa4-6a934ebb653d&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 7
<location path="scripts/publish-platform-index.sh" line_range="46" />
<code_context>
 podman manifest add "${options[@]}" "$manifest" "docker://$repository-amd@$amd_digest"
 podman manifest add "${options[@]}" "$manifest" "docker://$repository-arm@$arm_digest"
-podman manifest push --all "${options[@]}" --format=docker "$manifest" "docker://$destination"
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
</code_context>
<issue_to_address>
**issue (review_instructions):** This comment explains the behavior but does not provide a concrete organizational or structural suggestion.

Please make the guidance explicit—for example, state that this command should publish only the manifest index because child manifests are managed separately, and consider documenting that contract alongside the publishing test in `tests/integration/publishing/test-platform-publish.sh` for maintainability.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Ensure the comment provides a clear suggestion for improving the organization or structure of the code or documentation.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=5823e292-3e9c-49a0-ad36-4eb208fb9cbd&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 8
<location path="scripts/publish-platform-index.sh" line_range="49" />
<code_context>
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
 python3 - "$scratch/index.json" "$amd_digest" "$arm_digest" <<'PYTHON'
</code_context>
<issue_to_address>
**issue (review_instructions):** Preflight the referenced child manifests before publishing an index-only manifest.

This new mode assumes both child manifests and their blobs already exist at the destination; otherwise it can publish an index containing unavailable digests. Before this command, verify each child digest in the destination repository and fail with a clear error, for example: `podman manifest inspect "${options[@]}" "docker://$destination@$amd_digest" >/dev/null || { echo "missing AMD child manifest: $amd_digest" >&2; exit 1; }` (and repeat for `$arm_digest`).

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
The comment should address potential issues such as error handling or code robustness, and include specific code examples or modifications to illustrate the suggestion.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=5704ccf1-6084-47b5-9fbd-06d8a0441c56&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 9
<location path="scripts/publish-platform-index.sh" line_range="49" />
<code_context>
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
 python3 - "$scratch/index.json" "$amd_digest" "$arm_digest" <<'PYTHON'
</code_context>
<issue_to_address>
**issue (review_instructions):** Publishing only the index may leave its child manifests unavailable in the destination repository.

The manifest entries are sourced from `docker://$repository-amd` and `docker://$repository-arm`, while this command pushes only the index to `$destination`. Unless those child manifests have already been copied to the destination repository under the referenced digests, pulls of the published index can fail. Please verify that assumption or retain child-manifest copying where necessary.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Include comments that identify potential bugs or risks in the code, such as duplicate function definitions or mismatched interpreters.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=2a76282a-9a4e-4383-93f6-88481d98da0c&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 10
<location path="scripts/publish-platform-index.sh" line_range="49" />
<code_context>
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
 python3 - "$scratch/index.json" "$amd_digest" "$arm_digest" <<'PYTHON'
</code_context>
<issue_to_address>
**suggestion (review_instructions):** Handle push failures explicitly so a failed index publication cannot proceed to registry verification as if it succeeded.

Because this is a security-sensitive publication step, fail closed and emit actionable diagnostics. For example: `if ! podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"; then echo "failed to publish platform index to $destination" >&2; exit 1; fi`. If the script already uses `set -e`, document that assumption near this command instead.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Ensure the comment suggests handling exceptions appropriately if applicable.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=10c7919b-ce44-454b-aece-6a73c8875795&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 11
<location path="scripts/publish-platform-index.sh" line_range="49" />
<code_context>
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
 python3 - "$scratch/index.json" "$amd_digest" "$arm_digest" <<'PYTHON'
</code_context>
<issue_to_address>
**issue (review_instructions):** Disabling child-manifest push can leave the published index referencing manifests that do not exist at the destination.

This is only safe when both child manifests and their blobs are already present in the destination repository under the referenced digests. If `$repository-amd`/`$repository-arm` can differ from `$destination`, the index may be published successfully but pulls will fail with missing-manifest or missing-blob errors. Either verify destination availability before using `--all=false`, or retain `--all` when the repositories differ.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Identify and highlight potential bugs or risks in the code, such as missing syntax elements that could lead to unintended behavior.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=5326d396-ab1b-4134-b556-367c52b2b055&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 12
<location path="scripts/publish-platform-index.sh" line_range="49" />
<code_context>
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
+podman manifest push --all=false "${options[@]}" --format=docker "$manifest" "docker://$destination"
 podman manifest inspect "${options[@]}" "$destination" > "$scratch/index.json"
 python3 - "$scratch/index.json" "$amd_digest" "$arm_digest" <<'PYTHON'
</code_context>
<issue_to_address>
**issue (review_instructions):** Pushing only the index can publish references to missing child manifests or blobs.

This relies on both architecture-specific manifests and all referenced blobs already existing in the destination repository. If either source image was not previously pushed there, the registry can contain a dangling index and pulls will fail. Please either verify that prerequisite explicitly before this command (and fail with a clear error), or retain a fallback/explicit push of the child manifests when they are absent.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Identify potential bugs or risks in the code and provide a clear explanation of the issue; suggest specific improvements or revisions.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=f09cf967-42fb-43b3-896e-6b72e9cad5be&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

### Comment 13
<location path="scripts/publish-platform-index.sh" line_range="46" />
<code_context>
 podman manifest add "${options[@]}" "$manifest" "docker://$repository-amd@$amd_digest"
 podman manifest add "${options[@]}" "$manifest" "docker://$repository-arm@$arm_digest"
-podman manifest push --all "${options[@]}" --format=docker "$manifest" "docker://$destination"
+# Child manifests and blobs already exist in this repository. Copying them again
+# can recompress layers and replace the exact digests pinned above. Publish only
+# the index; keep the registry verification below strict.
</code_context>
<issue_to_address>
**suggestion (review_instructions):** Clarify the publish behavior with a concrete command example.

For example, explain that `--all=false` publishes only the index while retaining the existing child digests (such as `$amd_digest` and `$arm_digest`). This makes the intended difference from the previous `--all` behavior explicit and helps future maintainers avoid reintroducing layer copies or digest changes.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `*`

**Instructions:**
Verify that the comment includes a specific example or code suggestion to illustrate the proposed change.

[Edit this rule](https://app.sourcery.ai/accounts/150647/repo-settings/455664976?activeTab=review-rules&rule=a97c4304-e287-4165-bcb3-931d922acf66&from_surface=github_bot&intent=review_rules)

</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread scripts/publish-platform-index.sh Outdated
Comment thread scripts/publish-platform-index.sh Outdated
Comment thread scripts/publish-platform-index.sh Outdated
Comment thread scripts/publish-platform-index.sh Outdated
Comment thread scripts/publish-platform-index.sh Outdated
Comment thread scripts/publish-platform-index.sh Outdated
Comment thread scripts/publish-platform-index.sh Outdated
Comment thread scripts/publish-platform-index.sh Outdated
Comment thread scripts/publish-platform-index.sh Outdated
Comment thread scripts/publish-platform-index.sh
Signed-off-by: Ryan Cook <rcook@redhat.com>
@cooktheryan
cooktheryan enabled auto-merge (squash) October 8, 2026 20:52
@cooktheryan
cooktheryan disabled auto-merge October 8, 2026 20:52
@cooktheryan
cooktheryan merged commit 915a790 into main Oct 8, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant