Skip to content

Make addCRDs() resilient to unparseable manifests, and use shallow clones - #169

Open
dislbenn wants to merge 2 commits into
stolostron:mainfrom
dislbenn:fix/addcrds-resilient-yaml-parse
Open

Make addCRDs() resilient to unparseable manifests, and use shallow clones#169
dislbenn wants to merge 2 commits into
stolostron:mainfrom
dislbenn:fix/addcrds-resilient-yaml-parse

Conversation

@dislbenn

@dislbenn dislbenn commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Description

Two related fixes to the bundle-generation scripts: stop the whole tool crashing when a single bundle manifest can't be parsed as plain YAML, and stop doing full (unbounded-history) git clones for every component repo processed.

Related Issue

Found while investigating a failing stolostron/multiclusterhub-operator scheduled workflow run (run 31521675972): addCRDs() crashed with an uncaught yaml.parser.ParserError on a bundle manifest (networkpolicy.yaml in multicloud-operators-subscription) that embeds Helm/Go template syntax, which killed chart regeneration for every other component in the same run. Companion shallow-clone changes are also being made in stolostron/multiclusterhub-operator (stolostron/multiclusterhub-operator#4579) and stolostron/backplane-operator (stolostron/backplane-operator#3835), which each vendor a copy of generate-shell.py that clones this repo.

Changes Made

  1. addCRDs() resilience (bundles-to-charts.py): wraps the per-file YAML parse in try/except yaml.YAMLError, mirroring the existing pattern already used in find_templates_of_type() for the same kind of directory scan. A file that fails to parse is now logged with an UNPARSEABLE_MANIFEST: prefix (so callers can grep for exactly this condition instead of every routine WARNING the script already emits) and skipped, rather than crashing the whole run. This is intentionally non-fatal — it's not added to any error list that would fail the run — since a single malformed file in one upstream repo shouldn't block chart generation for every other component being processed in the same invocation.
  2. Shallow, single-branch clones (bundles-to-charts.py, generate-charts.py, move-charts.py): each of these clones every component repo with Repo.clone_from(url, path) (full history, every branch) and then separately checks out the target branch. None of these scripts read anything beyond current file contents on one branch (no git log/blame/diff-against-history usage anywhere in the codebase). Resolved the target branch before cloning and pass branch=/depth=1 to clone_from() directly, dropping the now-redundant separate checkout call.

Screenshots (if applicable)

N/A

Checklist

  • I have tested the changes locally and they are functioning as expected.
  • I have updated the documentation (if necessary) to reflect the changes.
  • I have added/updated relevant unit tests (if applicable).
  • I have ensured that my code follows the project's coding standards.
  • I have checked for any potential security issues and addressed them.
  • I have added necessary comments to the code, especially in complex or unclear sections.
  • I have rebased my branch on top of the latest main/master branch.

Additional Notes

Verified end-to-end against the real failure condition: pointed a scratch multiclusterhub-operator checkout's hack/bundle-automation/config.yaml at a fork branch of multicloud-operators-subscription still containing the original, unmodified networkpolicy.yaml (with the Helm template syntax intact), and ran generate-shell.py --update-charts-from-bundles against this patched branch.

  • Before this fix: uncaught yaml.parser.ParserError, exit code 2, entire run fails.
  • After this fix: exit code 0, "All repositories and operators processed successfully," every other component's chart still generated correctly, and the skipped file is clearly flagged: UNPARSEABLE_MANIFEST: Skipped 'networkpolicy.yaml' for operator 'multicloud-operators-subscription' while scanning for CRDs — file appears to contain Go/Helm template syntax ('{{ }}'), which is not valid standalone YAML.
  • Grepping the full run log for WARNING case-insensitively matches 81 lines (mostly routine, expected output); grepping for UNPARSEABLE_MANIFEST matches exactly the 1 line that's actually actionable.
  • For the shallow-clone change: as a concrete before/after, cloning a component repo with a long release history dropped from a 6.2M .git directory (full clone) to 992K (shallow, single-branch), with no other code depending on git history for these repos.
  • All three modified Python files pass python3 -m py_compile.

Reviewers

/cc @cameronmwall @ngraham20 @gparvin @msmigiel-rh

Definition of Done

  • Code is reviewed.
  • Code is tested.
  • Documentation is updated.
  • All checks and tests pass.
  • Approved by at least one reviewer.
  • Merged into the main/master branch.

Summary by CodeRabbit

  • Bug Fixes

    • Bundle generation now continues when individual YAML manifests cannot be parsed, while recording skipped files for review.
    • CRD detection continues to support manifests containing multiple documents.
  • Improvements

    • Repository processing now supports selecting configured or overridden branches during cloning.
    • Cloning uses shallow, single-branch downloads to reduce processing time and storage requirements.
    • Logs now show the selected repository branch.

@openshift-ci

openshift-ci Bot commented Aug 11, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: dislbenn

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

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@dislbenn, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eda987d3-6e71-453a-98b0-62beee5d14f7

📥 Commits

Reviewing files that changed from the base of the PR and between 38f94b9 and d55ebae.

📒 Files selected for processing (3)
  • scripts/bundle-generation/bundles-to-charts.py
  • scripts/bundle-generation/generate-charts.py
  • scripts/bundle-generation/move-charts.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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 `@scripts/bundle-generation/bundles-to-charts.py`:
- Around line 2022-2038: Resolve one canonical branch per workflow and reuse it
consistently: in scripts/bundle-generation/bundles-to-charts.py lines 2022-2038,
propagate branch_to_use from the clone step through all downstream version and
resource calls, while keeping default-branch resolution reachable. In
scripts/bundle-generation/generate-charts.py lines 1773-1790, replace the empty
branch value with the actual checked-out default branch. In
scripts/bundle-generation/move-charts.py lines 355-365, replace the synthetic
"main" value with that actual checked-out default branch.
- Around line 1506-1508: Update the addCRDs documentation to accurately describe
all currently propagated failure types: ValueError for bundlePath validation,
OSError from shutil.copyfile failures, and SystemExit from derived bundle lookup
terminating via sys.exit(1); alternatively, normalize these failures to a single
documented exception type.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e2ce1cc-fca7-4a7c-a9e4-0d2b485bc185

📥 Commits

Reviewing files that changed from the base of the PR and between 38f94b9 and 561d8af.

📒 Files selected for processing (3)
  • scripts/bundle-generation/bundles-to-charts.py
  • scripts/bundle-generation/generate-charts.py
  • scripts/bundle-generation/move-charts.py

Comment on lines 1506 to 1508
Raises:
ValueError: If bundlePath is not found or if CRD file copying fails.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 'Raises:|ValueError|shutil\.copyfile|sys\.exit\(1\)' \
  scripts/bundle-generation/bundles-to-charts.py

python - <<'PY'
import shutil
import tempfile
from pathlib import Path

root = Path(tempfile.mkdtemp())
try:
    shutil.copyfile(root / "missing", root / "destination")
except Exception as exc:
    assert isinstance(exc, OSError), type(exc)
    print(type(exc).__name__)
else:
    raise SystemExit("Expected copyfile to fail")
PY

Repository: stolostron/installer-dev-tools

Length of output: 13747


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1450,1628p' scripts/bundle-generation/bundles-to-charts.py
printf '\naddCRDs call sites:\n'
rg -n -C 4 'addCRDs|getBundleManifestsPath' scripts/bundle-generation/bundles-to-charts.py

Repository: stolostron/installer-dev-tools

Length of output: 11481


Document the actual exceptions raised by addCRDs.

ValueError covers only explicit bundlePath validation. shutil.copyfile propagates OSError, and derived bundle lookup can terminate with SystemExit through sys.exit(1). Document these behaviors or normalize them to one exception type.

🤖 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 `@scripts/bundle-generation/bundles-to-charts.py` around lines 1506 - 1508,
Update the addCRDs documentation to accurately describe all currently propagated
failure types: ValueError for bundlePath validation, OSError from
shutil.copyfile failures, and SystemExit from derived bundle lookup terminating
via sys.exit(1); alternatively, normalize these failures to a single documented
exception type.

Comment on lines 2022 to +2038
# Check for branch override first, then use config branch
if repo_name in component_branch_overrides:
branch_to_use = component_branch_overrides[repo_name]
logging.info(f"Using branch override for {repo_name}: {branch_to_use}")
else:
branch_to_use = branch

repository.git.checkout(branch_to_use)
logging.info("Cloning repository: %s from %s (branch=%s)", repo_name, git_url, branch_to_use)
repo_path = os.path.join(SCRIPT_DIR, "tmp", repo_name)

if os.path.exists(repo_path):
shutil.rmtree(repo_path)

# Shallow, single-branch clone: only the tip commit of the target
# branch is needed, since this script only reads current file
# contents (CSVs/CRDs/manifests) and never inspects history.
Repo.clone_from(git_url, repo_path, branch=branch_to_use, depth=1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Resolve one canonical branch before cloning and reuse it throughout each workflow.

The clone target can differ from the branch value used by downstream version and resource logic. This can produce charts from one branch while applying rules for another branch.

  • scripts/bundle-generation/bundles-to-charts.py#L2022-L2038: propagate branch_to_use to all downstream calls and make default-branch resolution reachable.
  • scripts/bundle-generation/generate-charts.py#L1773-L1790: replace the empty branch value with the actual checked-out default branch.
  • scripts/bundle-generation/move-charts.py#L355-L365: replace the synthetic "main" value with the actual checked-out default branch.
📍 Affects 3 files
  • scripts/bundle-generation/bundles-to-charts.py#L2022-L2038 (this comment)
  • scripts/bundle-generation/generate-charts.py#L1773-L1790
  • scripts/bundle-generation/move-charts.py#L355-L365
🤖 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 `@scripts/bundle-generation/bundles-to-charts.py` around lines 2022 - 2038,
Resolve one canonical branch per workflow and reuse it consistently: in
scripts/bundle-generation/bundles-to-charts.py lines 2022-2038, propagate
branch_to_use from the clone step through all downstream version and resource
calls, while keeping default-branch resolution reachable. In
scripts/bundle-generation/generate-charts.py lines 1773-1790, replace the empty
branch value with the actual checked-out default branch. In
scripts/bundle-generation/move-charts.py lines 355-365, replace the synthetic
"main" value with that actual checked-out default branch.

dislbenn added a commit to dislbenn/backplane-operator that referenced this pull request Aug 11, 2026
Mirrors the same change made in stolostron/multiclusterhub-operator
(stolostron/multiclusterhub-operator#4579). The three scheduled
automation workflows here (regenerate-charts.yml,
regenerate-operator-bundles.yml, resync-owner-file.yml) all use
peter-evans/create-pull-request with no body input, so every generated
PR gets the same generic default text with no information about what
changed or whether anything needs a closer look.

Add two purpose-built PR templates under .github/PULL_REQUEST_TEMPLATE/
(kept separate from the default human-authored pull_request_template.md,
since a bot can't honestly assert things like "I tested this locally"):

- automated-bundle-update.md: used by regenerate-charts.yml and
  regenerate-operator-bundles.yml. Reports the triggering run, a git
  diff --stat summary, and any manifest files skipped during CRD
  scanning (tagged UNPARSEABLE_MANIFEST in the log, matching the
  installer-dev-tools addCRDs() resilience fix in
  stolostron/installer-dev-tools#169) so a reviewer can verify none of
  them were meant to be a CRD.
- automated-owners-resync.md: used by resync-owner-file.yml, a
  simpler variant with no warnings section since that workflow has no
  chart/CRD-generation risk profile.

hack/scripts/render_pr_body.py (identical copy of the one added in
multiclusterhub-operator) fills in each template's
`<!-- AUTOMATION:NAME -->` markers with generated content and writes
the result to a file, which is passed to create-pull-request via
body-path instead of the default body text.

Signed-off-by: dislbenn <dbennett@redhat.com>
Signed-off-by: dislbenn <dbennett@redhat.com>
bundles-to-charts.py, generate-charts.py, and move-charts.py each clone
every upstream component repo with a full, unbounded git clone (no
--depth limit), then separately check out the target branch locally.
None of these scripts read anything beyond the current file contents
on that one branch (no git log/blame/diff-against-history usage), so
the full commit history was never needed.

Switch each clone to a shallow, single-branch clone (depth=1, branch=
<target>) resolved up front, and drop the now-redundant separate
checkout step. Verified against a component repo with a long release
history: .git directory size dropped from 6.2M to 992K for a single
clone, and this applies to every component processed on every
scheduled run.

Signed-off-by: dislbenn <dbennett@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant