Skip to content

feat: add support for helm vendoring#846

Merged
lockwobr merged 2 commits into
mainfrom
feat/helm-vendor
May 11, 2026
Merged

feat: add support for helm vendoring#846
lockwobr merged 2 commits into
mainfrom
feat/helm-vendor

Conversation

@lockwobr

Copy link
Copy Markdown
Contributor

Summary

Add --vendor-charts opt-in flag (CLI + API + config-file) that pulls upstream Helm chart bytes into the bundle at bundle time, producing a self-contained, air-gap-deployable artifact with a provenance.yaml audit log.

Motivation / Context

The uniform numbered local-chart layout from #662 still references upstream Helm chart registries at deploy time, so
bundles can't deploy in air-gapped clusters, behind flaky networks, or against private registries that aren't reachable
at install time. --vendor-charts collapses every Helm-typed component into a single wrapped local chart with the
upstream tarball checked in at NNN-<name>/charts/<chart>-<version>.tgz, eliminating deploy-time registry egress.

Vendoring is intentionally opt-in for two reasons documented inline:

  • CVE-yank fail-loud signal preservation. Non-vendored bundles fail at deploy time when an upstream chart version is yanked. Vendored bundles freeze the bytes and outlive the yank — provenance.yaml exposes the audit surface for cross-referencing yank lists.
  • Bundle-time cost. Vendoring adds network egress, auth surface, and ~0.5–5 MB per chart. Users who don't need
    air-gap shouldn't pay.

Closes: #663, #516
Related: #516 (epic), #662 (predecessor layout)

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Build/CI/tooling

Component(s) Affected

  • CLI (cmd/aicr, pkg/cli)
  • API server (cmd/aicrd, pkg/api, pkg/server)
  • Recipe engine / data (pkg/recipe)
  • Bundlers (pkg/bundler, pkg/component/*)
  • Collectors / snapshotter (pkg/collector, pkg/snapshotter)
  • Validator (pkg/validator)
  • Core libraries (pkg/errors, pkg/k8s)
  • Docs/examples (docs/, examples/)
  • Other: ____________

Implementation Notes

ChartPuller shim, not the Helm SDK (yet). Vendoring helm.sh/helm/v4/pkg/downloader transitively pulls in github.com/cyphar/filepath-securejoin (MPL-2.0), which the repo allowlist (Apache-2.0/MIT/BSD/ISC/Zlib only — explicitly "DO NOT ADD IGNORES") rejects. To unblock, the bundler calls a ChartPuller interface in pkg/bundler/deployer/localformat/vendor.go. Today's implementation is CLIChartPuller, which shells out to helm pull via exec.CommandContext (no shell). When legal approves the SDK, swap in an SDKChartPuller alongside; nothing else changes.

Single wrapped folder per component. Mixed components (Helm + raw manifests) used to emit primary + -post two-folder shape (#662). With --vendor-charts, they collapse into one folder: wrapper Chart.yaml declares the vendored chart as a dependencies: entry with empty repository: (Helm resolves from charts/<chart>-<ver>.tgz adjacent), values are nested under the subchart name so Helm forwards them, and recipe-side raw manifests live in templates/ with helm.sh/hook: post-install + stable hook-weight injected.

Provenance is shared, K8s-shape YAML. pkg/bundler/deployer/localformat/WriteProvenance emits provenance.yaml at the bundle root with apiVersion: aicr.nvidia.com/v1alpha1, kind: BundleProvenance, and one entry per vendored chart (name, chart, version, repository, sha256, tarballName, pullerVersion). Called by all three deployer Generators — helm, argocd, and argocd-helm — so air-gap audit works regardless of --deployer. Format matches every other AICR persisted file (Recipe, Snapshot, RecipeCriteria); pipe through yq -o=json for tools that expect JSON.

Hook injection uses gopkg.in/yaml.v3 for streaming document boundaries — handles leading ---, --- inside literal-block strings, and interleaved comments correctly.

Write returns a struct (WriteResult). Previous ([]Folder, error) became ([]Folder, []VendorRecord, error) while iterating; settled on WriteResult{Folders, VendoredCharts} so future fields don't keep breaking the signature.

Auth flows through Helm conventions unchanged: HELM_REPOSITORY_USERNAME/HELM_REPOSITORY_PASSWORD for HTTP(S); docker config (~/.docker/config.json or $DOCKER_CONFIG) for OCI. Documented in docs/user/cli-reference.md ("Vendoring Charts for Air-Gap" section) and docs/user/api-reference.md.

Testing

# Commands run (prefer `make qualify` for non-trivial changes)
make qualify

Risk Assessment

  • Low — Isolated change, well-tested, easy to revert
  • Medium — Touches multiple components or has broader impact
  • High — Breaking change, affects critical paths, or complex rollout

Rollout notes:

Checklist

  • Tests pass locally (make test with -race)
  • Linter passes (make lint)
  • I did not skip/disable tests to make CI green
  • I added/updated tests for new functionality
  • I updated docs if user-facing behavior changed
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S) — GPG signing info

@coderabbitai

This comment was marked as resolved.

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

Caution

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

⚠️ Outside diff range comments (1)
pkg/bundler/deployer/localformat/writer_test.go (1)

37-50: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Move res.Folders access after the error check to avoid nil dereference in failing paths.

Line 48/115/175/258/311 reads res.Folders before validating err. If Write fails and returns res == nil, the test panics and hides the real failure reason.

Suggested fix pattern (apply to each affected test)
 res, err := localformat.Write(context.Background(), localformat.Options{
   ...
 })
-folders := res.Folders
 if err != nil {
   t.Fatalf("Write: %v", err)
 }
+folders := res.Folders

Also applies to: 82-117, 141-177, 227-260, 300-313

🤖 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 `@pkg/bundler/deployer/localformat/writer_test.go` around lines 37 - 50, The
tests call localformat.Write(...) and immediately access res.Folders before
checking err, which can nil-deref if Write fails; in each affected test (e.g.,
the block that calls localformat.Write in writer_test.go) move the statement
"folders := res.Folders" (or any use of res) to after the error check "if err !=
nil { t.Fatalf... }" so you first assert err is nil and only then read
res.Folders; update all similar occurrences around the localformat.Write calls
referenced (lines near the blocks using res, err, folders) to follow this
pattern.
🤖 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 `@api/aicr/v1/server.yaml`:
- Around line 894-903: Update the parameter description for the boolean
"vendor-charts" option in the schema so it documents runtime prerequisites and
failure modes: mention that vendoring requires a working Helm client and any
necessary upstream auth/registry credentials on the host performing bundling
(Helm and auth must be available at bundle time), and state the observable
failure behavior when those prerequisites are missing (the bundle request will
fail and return an error indicating the missing Helm/tooling or credentials
rather than producing a vendored chart). Edit the existing description text (the
"vendor-charts" description block adjacent to the schema:type boolean/default
false) to append these prerequisite and failure-mode sentences so API clients
can reason about outcomes without cross-referencing other docs.

In `@docs/user/cli-reference.md`:
- Around line 1107-1173: The fenced code block that shows the bundle layout
("my-bundle/ ...") in the "Vendoring Charts for Air-Gap" section is missing a
language identifier and triggers markdownlint; update the opening fence for that
block to include a language specifier (e.g., change ``` to ```text) so the block
becomes fenced with a language, targeting the block that begins with
"my-bundle/" in docs/user/cli-reference.md.

In `@pkg/bundler/deployer/localformat/provenance.go`:
- Around line 80-112: Change WriteProvenance to accept a context.Context
parameter and use it for cancellable I/O: update the signature of
WriteProvenance(ctx context.Context, outputDir string, records []VendorRecord),
propagate the new ctx through its callers in pkg/bundler/deployer/helm/helm.go
and pkg/bundler/deployer/argocd/argocd.go, check ctx.Err() before doing file
operations (after computing abs via deployer.SafeJoin), and perform the file
write in a context-aware way (e.g., start the write in a goroutine and select on
ctx.Done() to cancel or return an error if canceled, or use an OS file handle
and ensure you abort/close on ctx cancellation) instead of directly calling
os.WriteFile; keep existing error wrapping and return values, and ensure you
still use ProvenanceFileName and deployer.SafeJoin as before.

In `@pkg/cli/bundle.go`:
- Around line 400-409: Update the help text for the vendor-charts CLI flag so it
references the actual output filename provenance.yaml instead of
provenance.json: locate the cli.BoolFlag with Name: "vendor-charts" in bundle.go
and edit the Usage string to replace "provenance.json" with "provenance.yaml" so
the documented filename matches the vendoring behavior.

---

Outside diff comments:
In `@pkg/bundler/deployer/localformat/writer_test.go`:
- Around line 37-50: The tests call localformat.Write(...) and immediately
access res.Folders before checking err, which can nil-deref if Write fails; in
each affected test (e.g., the block that calls localformat.Write in
writer_test.go) move the statement "folders := res.Folders" (or any use of res)
to after the error check "if err != nil { t.Fatalf... }" so you first assert err
is nil and only then read res.Folders; update all similar occurrences around the
localformat.Write calls referenced (lines near the blocks using res, err,
folders) to follow this pattern.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 810fafe1-1721-4e55-b602-055beb726d28

📥 Commits

Reviewing files that changed from the base of the PR and between ad5d9f6 and 19b5e50.

📒 Files selected for processing (28)
  • api/aicr/v1/server.yaml
  • docs/contributor/api-server.md
  • docs/contributor/cli.md
  • docs/user/api-reference.md
  • docs/user/cli-reference.md
  • pkg/bundler/bundler.go
  • pkg/bundler/config/config.go
  • pkg/bundler/config/config_test.go
  • pkg/bundler/deployer/argocd/argocd.go
  • pkg/bundler/deployer/argocdhelm/argocdhelm.go
  • pkg/bundler/deployer/helm/helm.go
  • pkg/bundler/deployer/localformat/provenance.go
  • pkg/bundler/deployer/localformat/provenance_test.go
  • pkg/bundler/deployer/localformat/templates/wrapper-chart.yaml.tmpl
  • pkg/bundler/deployer/localformat/vendor.go
  • pkg/bundler/deployer/localformat/vendor_folder.go
  • pkg/bundler/deployer/localformat/vendor_test.go
  • pkg/bundler/deployer/localformat/vendor_wrapper.go
  • pkg/bundler/deployer/localformat/vendor_wrapper_test.go
  • pkg/bundler/deployer/localformat/vendor_write_test.go
  • pkg/bundler/deployer/localformat/writer.go
  • pkg/bundler/deployer/localformat/writer_test.go
  • pkg/bundler/handler.go
  • pkg/cli/bundle.go
  • pkg/config/config.go
  • pkg/config/resolve.go
  • pkg/defaults/timeouts.go
  • tests/chainsaw/cli/bundle-vendor-charts/chainsaw-test.yaml

Comment thread api/aicr/v1/server.yaml
Comment thread docs/user/cli-reference.md
Comment thread pkg/bundler/deployer/localformat/provenance.go Outdated
Comment thread pkg/cli/bundle.go
@github-actions

github-actions Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Coverage Report ✅

Metric Value
Coverage 76.7%
Threshold 75%
Status Pass
Coverage Badge
![Coverage](https://img.shields.io/badge/coverage-76.7%25-green)

Merging this branch changes the coverage (4 decrease, 2 increase)

Impacted Packages Coverage Δ 🤖
github.com/NVIDIA/aicr/pkg/bundler 61.33% (-0.38%) 👎
github.com/NVIDIA/aicr/pkg/bundler/config 96.92% (+0.05%) 👍
github.com/NVIDIA/aicr/pkg/bundler/deployer/argocd 80.30% (-6.25%) 👎
github.com/NVIDIA/aicr/pkg/bundler/deployer/argocdhelm 80.85% (ø)
github.com/NVIDIA/aicr/pkg/bundler/deployer/helm 84.21% (-3.64%) 👎
github.com/NVIDIA/aicr/pkg/bundler/deployer/localformat 72.85% (-0.87%) 👎
github.com/NVIDIA/aicr/pkg/cli 62.92% (ø)
github.com/NVIDIA/aicr/pkg/config 95.11% (+0.02%) 👍
github.com/NVIDIA/aicr/pkg/defaults 100.00% (ø)

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/NVIDIA/aicr/pkg/bundler/bundler.go 56.90% (ø) 406 231 175
github.com/NVIDIA/aicr/pkg/bundler/config/config.go 96.36% (+0.07%) 165 (+3) 159 (+3) 6 👍
github.com/NVIDIA/aicr/pkg/bundler/deployer/argocd/argocd.go 80.30% (-6.25%) 132 (+13) 106 (+3) 26 (+10) 👎
github.com/NVIDIA/aicr/pkg/bundler/deployer/argocdhelm/argocdhelm.go 80.85% (ø) 355 287 68
github.com/NVIDIA/aicr/pkg/bundler/deployer/helm/helm.go 84.21% (-3.64%) 114 (+7) 96 (+2) 18 (+5) 👎
github.com/NVIDIA/aicr/pkg/bundler/deployer/localformat/provenance.go 82.35% (+82.35%) 17 (+17) 14 (+14) 3 (+3) 🌟
github.com/NVIDIA/aicr/pkg/bundler/deployer/localformat/vendor.go 60.44% (+60.44%) 91 (+91) 55 (+55) 36 (+36) 🌟
github.com/NVIDIA/aicr/pkg/bundler/deployer/localformat/vendor_folder.go 70.53% (+70.53%) 95 (+95) 67 (+67) 28 (+28) 🌟
github.com/NVIDIA/aicr/pkg/bundler/deployer/localformat/vendor_wrapper.go 75.86% (+75.86%) 58 (+58) 44 (+44) 14 (+14) 🌟
github.com/NVIDIA/aicr/pkg/bundler/deployer/localformat/writer.go 70.47% (+5.32%) 149 (+17) 105 (+19) 44 (-2) 👍
github.com/NVIDIA/aicr/pkg/bundler/handler.go 74.45% (-2.06%) 137 (+5) 102 (+1) 35 (+4) 👎
github.com/NVIDIA/aicr/pkg/cli/bundle.go 41.78% (ø) 146 61 85
github.com/NVIDIA/aicr/pkg/config/config.go 0.00% (ø) 0 0 0
github.com/NVIDIA/aicr/pkg/config/resolve.go 100.00% (ø) 95 (+1) 95 (+1) 0
github.com/NVIDIA/aicr/pkg/defaults/timeouts.go 0.00% (ø) 0 0 0

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

@lockwobr lockwobr force-pushed the feat/helm-vendor branch 2 times, most recently from cdc1084 to 01afd79 Compare May 11, 2026 21:48

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

♻️ Duplicate comments (2)
docs/user/cli-reference.md (1)

1122-1140: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix fenced code block language identifier.

The fenced code block showing the bundle layout is missing a language specifier, triggering a markdownlint warning (MD040). Add text as the language identifier.

📝 Suggested fix
-```
+```text
 my-bundle/
   001-gpu-operator/
     Chart.yaml                     # wrapper, declares the vendored subchart

Based on learnings: As per coding guidelines, ensure all markdown files pass yamllint/markdownlint validation before submitting.

🤖 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 `@docs/user/cli-reference.md` around lines 1122 - 1140, The fenced code block
showing the bundle layout in docs/user/cli-reference.md is missing a language
identifier (MD040); update the opening fence from ``` to ```text so the block
starts with ```text to satisfy markdownlint and mark the snippet as plain text.
pkg/bundler/deployer/localformat/provenance.go (1)

80-112: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Add context-aware cancellation to provenance file writes.

WriteProvenance performs filesystem I/O but has no context.Context parameter, so callers cannot enforce timeout/cancellation during bundle generation.

Proposed direction
-func WriteProvenance(outputDir string, records []VendorRecord) (string, int64, error) {
+func WriteProvenance(ctx context.Context, outputDir string, records []VendorRecord) (string, int64, error) {
+	if err := ctx.Err(); err != nil {
+		return "", 0, errors.Wrap(errors.ErrCodeTimeout, "context cancelled", err)
+	}
 	if len(records) == 0 {
 		return "", 0, errors.New(errors.ErrCodeInternal,
 			"WriteProvenance: empty records slice")
 	}
 	...
+	if err := ctx.Err(); err != nil {
+		return "", 0, errors.Wrap(errors.ErrCodeTimeout, "context cancelled", err)
+	}
 	if err = os.WriteFile(abs, body, 0o644); err != nil {
 		...
 	}

Also update all call sites to pass the already-available request/bundle context.

As per coding guidelines: "All I/O operations must use context with timeout" and "Never use context.Background() in I/O methods."

🤖 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 `@pkg/bundler/deployer/localformat/provenance.go` around lines 80 - 112, Change
WriteProvenance to accept a context.Context (e.g., func WriteProvenance(ctx
context.Context, outputDir string, records []VendorRecord) ...) and update all
call sites to pass the request/bundle context; keep the existing sort/marshal
and SafeJoin (deployer.SafeJoin) usage but perform a ctx.Err() check
before/after expensive steps and use a context-aware file write instead of
os.WriteFile (open the file with os.Create/os.OpenFile and perform the write
while watching ctx.Done(), aborting and returning context.Canceled or a wrapped
error if canceled); ensure the file is closed on all paths and preserve existing
error wrapping and returned values (file path and byte length) for
provenanceFile/ProvenanceFileName flows.
🤖 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 `@pkg/bundler/config/config_test.go`:
- Around line 84-94: The test TestConfigVendorCharts uses three separate
assertions; convert it into a table-driven test to match the suite pattern:
define a slice of test cases each with a name, optional config option (nil or
WithVendorCharts(true/false)), and expected bool, then loop over cases calling
NewConfig(...) and c.VendorCharts() and use t.Run(name, func(t *testing.T){ if
got != want { t.Errorf(...) } }). Reference TestConfigVendorCharts, NewConfig,
WithVendorCharts and VendorCharts when locating the test to replace.

In `@pkg/bundler/deployer/localformat/vendor_folder.go`:
- Around line 205-267: The loop in writeMixedManifests can run long and should
respect cancellation; update writeMixedManifests to accept a context.Context
(add ctx parameter to its signature and update all callers), and at the top of
the for _, p := range sortedPaths loop do a ctx cancellation check (select {
case <-ctx.Done(): return nil, errors.Wrap(errors.ErrCodeInternal, "operation
cancelled", ctx.Err()) default: }) before calling manifest.Render,
injectPostInstallHooks or writeFile so work stops promptly on cancellation;
ensure the new ctx is threaded through any helper calls that may need it.

In `@pkg/bundler/deployer/localformat/vendor_test.go`:
- Around line 26-48: The test TestValidateForPull currently only checks presence
of an error (wantErr) but ignores the expected error code in the test vector
(code); update the loop that calls validateForPull to assert the specific error
code: when tt.wantErr is true ensure err != nil and if tt.code != "" type-assert
the returned error to an interface with Code() string (or use the concrete
structured error type used in this package) and compare Code() == tt.code,
falling back to checking err.Error() contains tt.code if the structured type is
not present; when tt.wantErr is false ensure err == nil. Reference
validateForPull and the test table (tests / TestValidateForPull) to locate where
to add these assertions.

In `@pkg/bundler/deployer/localformat/vendor_write_test.go`:
- Around line 171-172: The test currently ignores the error returned by
os.ReadDir(outDir) which can lead to false-positive passes; update the call that
sets entries (the os.ReadDir(outDir) invocation and its result assigned to
entries) to capture the error, assert or fail on non-nil error (e.g., t.Fatalf
or require.NoError) before iterating over entries (for _, e := range entries) so
the test fails loudly when directory enumeration fails.

In `@pkg/bundler/deployer/localformat/vendor.go`:
- Around line 225-231: Replace the substring-only detection with sentinel error
checks before the fallback: in the block that examines runErr and combined
(using symbols runErr and combined), first check errors.Is(runErr,
exec.ErrNotFound) || errors.Is(runErr, os.ErrNotExist) and if true return the
wrapped errors.Wrap(errors.ErrCodeUnavailable, "vendor-charts: helm CLI not
found on PATH (install helm or unset --vendor-charts)", runErr); only if those
sentinel checks fail, keep the existing strings.Contains(combined, "...")
fallback to preserve behavior for non-sentinel cases.

---

Duplicate comments:
In `@docs/user/cli-reference.md`:
- Around line 1122-1140: The fenced code block showing the bundle layout in
docs/user/cli-reference.md is missing a language identifier (MD040); update the
opening fence from ``` to ```text so the block starts with ```text to satisfy
markdownlint and mark the snippet as plain text.

In `@pkg/bundler/deployer/localformat/provenance.go`:
- Around line 80-112: Change WriteProvenance to accept a context.Context (e.g.,
func WriteProvenance(ctx context.Context, outputDir string, records
[]VendorRecord) ...) and update all call sites to pass the request/bundle
context; keep the existing sort/marshal and SafeJoin (deployer.SafeJoin) usage
but perform a ctx.Err() check before/after expensive steps and use a
context-aware file write instead of os.WriteFile (open the file with
os.Create/os.OpenFile and perform the write while watching ctx.Done(), aborting
and returning context.Canceled or a wrapped error if canceled); ensure the file
is closed on all paths and preserve existing error wrapping and returned values
(file path and byte length) for provenanceFile/ProvenanceFileName flows.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 20c62d1c-1d9b-446a-8e71-ed9098ae792f

📥 Commits

Reviewing files that changed from the base of the PR and between 19b5e50 and cdc1084.

📒 Files selected for processing (28)
  • api/aicr/v1/server.yaml
  • docs/contributor/api-server.md
  • docs/contributor/cli.md
  • docs/user/api-reference.md
  • docs/user/cli-reference.md
  • pkg/bundler/bundler.go
  • pkg/bundler/config/config.go
  • pkg/bundler/config/config_test.go
  • pkg/bundler/deployer/argocd/argocd.go
  • pkg/bundler/deployer/argocdhelm/argocdhelm.go
  • pkg/bundler/deployer/helm/helm.go
  • pkg/bundler/deployer/localformat/provenance.go
  • pkg/bundler/deployer/localformat/provenance_test.go
  • pkg/bundler/deployer/localformat/templates/wrapper-chart.yaml.tmpl
  • pkg/bundler/deployer/localformat/vendor.go
  • pkg/bundler/deployer/localformat/vendor_folder.go
  • pkg/bundler/deployer/localformat/vendor_test.go
  • pkg/bundler/deployer/localformat/vendor_wrapper.go
  • pkg/bundler/deployer/localformat/vendor_wrapper_test.go
  • pkg/bundler/deployer/localformat/vendor_write_test.go
  • pkg/bundler/deployer/localformat/writer.go
  • pkg/bundler/deployer/localformat/writer_test.go
  • pkg/bundler/handler.go
  • pkg/cli/bundle.go
  • pkg/config/config.go
  • pkg/config/resolve.go
  • pkg/defaults/timeouts.go
  • tests/chainsaw/cli/bundle-vendor-charts/chainsaw-test.yaml

Comment thread pkg/bundler/config/config_test.go
Comment thread pkg/bundler/deployer/localformat/vendor_folder.go Outdated
Comment thread pkg/bundler/deployer/localformat/vendor_test.go
Comment thread pkg/bundler/deployer/localformat/vendor_write_test.go Outdated
Comment thread pkg/bundler/deployer/localformat/vendor.go Outdated
coderabbitai[bot]

This comment was marked as resolved.

@lockwobr lockwobr force-pushed the feat/helm-vendor branch from 01afd79 to 62fbb16 Compare May 11, 2026 22:01

@mchmarny mchmarny left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Solid shape overall — the ChartPuller interface boundary, the documented license rationale for shelling out vs SDK, the WriteResult struct for forward-compat, and the hermetic chainsaw fake-helm shim are all clean choices. The provenance.yaml audit log emitted from all three deployers is exactly the right place for the bundle-time freeze record.

One medium concern on validateForPull: the HTTP(S) branch of helm pull passes chartName as a separate argv token, so a chart name beginning with -- (e.g., --insecure-skip-tls-verify) injects as a helm flag rather than a chart name. The OCI branch is safe (single URL token). Recipes are repo-controlled today, but --vendor-charts also flows through the API surface so input validation at the boundary is the right defense.

Three lows: user-visible error message references provenance.json while the file is provenance.yaml (five stale references total, only helm.go:204 is user-visible); injectHookOnDoc can silently overwrite existing annotations on an unexpected map type; helm.sh/hook-delete-policy: before-hook-creation is unconditional and may surprise mixed-component recipes that include stateful resources.

CI: lint, build, security scan, malware-scan all green at review time; Test, E2E, analyze, GPU smoke still in flight; mergeable_state: blocked (review gate). Nothing blocks merge from this review.

Comment thread pkg/bundler/deployer/localformat/vendor.go
Comment thread pkg/bundler/deployer/helm/helm.go Outdated
Comment thread pkg/bundler/deployer/localformat/vendor_wrapper.go
Comment thread pkg/bundler/deployer/localformat/vendor_wrapper.go Outdated
Comment thread pkg/bundler/deployer/localformat/vendor.go
Comment thread tests/chainsaw/cli/bundle-vendor-charts/chainsaw-test.yaml

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

♻️ Duplicate comments (1)
pkg/bundler/deployer/helm/helm.go (1)

99-103: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix stale provenance.json references to provenance.yaml.

Line 204 surfaces "failed to generate provenance.json" even though the writer emits provenance.yaml; this can mislead operators during failures. The same mismatch appears in the field comment near Line 100.

Suggested patch
-	// vendorRecords is populated by Generate when VendorCharts is on.
-	// Captured here so generateProvenanceFile can write provenance.json
+	// vendorRecords is populated by Generate when VendorCharts is on.
+	// Captured here so Generate can write provenance.yaml
 	// without re-threading the slice through every helper call. The
 	// field is unset (nil) when VendorCharts is off.
 	vendorRecords []localformat.VendorRecord
@@
-	// Generate provenance.json for vendored bundles. Written before
+	// Generate provenance.yaml for vendored bundles. Written before
 	// checksums.txt so the audit file is itself checksummed.
 	if len(g.vendorRecords) > 0 {
 		provPath, provSize, provErr := localformat.WriteProvenance(ctx, outputDir, g.vendorRecords)
 		if provErr != nil {
 			return nil, errors.Wrap(errors.ErrCodeInternal,
-				"failed to generate provenance.json", provErr)
+				"failed to generate provenance.yaml", provErr)
 		}

Also applies to: 198-205

🤖 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 `@pkg/bundler/deployer/helm/helm.go` around lines 99 - 103, The struct field
comment for vendorRecords and the error message inside generateProvenanceFile
incorrectly reference "provenance.json" even though the writer emits
"provenance.yaml"; update the comment on vendorRecords to say provenance.yaml
and change the error string in generateProvenanceFile (the log/return that says
"failed to generate provenance.json") to "failed to generate provenance.yaml" so
both documentation and runtime errors match the actual output file name.
🤖 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 `@pkg/bundler/deployer/localformat/vendor_test.go`:
- Around line 82-112: Test table only exercised substring matching because every
runErr was stderrors.New("exit status 1"); add cases that pass the sentinel
errors so the errors.Is(...) branch in classifyHelmCLIError is covered. Update
TestClassifyHelmCLIError's test struct to include a runErr field and add rows
with runErr = exec.ErrNotFound and runErr = os.ErrNotExist (with appropriate
names and expected ErrCodeUnavailable), and use that runErr when calling
classifyHelmCLIError instead of the hardcoded stderrors.New; this will ensure
the typed sentinel-path is regression-proof.

In `@pkg/bundler/deployer/localformat/vendor_write_test.go`:
- Around line 248-259: The test calls localformat.Write with
context.Background(), which can hang; replace that with a timeout context (e.g.,
ctx, cancel := context.WithTimeout(context.Background(), time.Second*X)) and
pass ctx into localformat.Write, ensuring you defer cancel() to release
resources; update the call site around the localformat.Write invocation
(referencing localformat.Write, localformat.Options and fakePuller) and
import/time usage accordingly.

In `@pkg/bundler/deployer/localformat/writer.go`:
- Around line 49-65: The comments on the WriteResult type still refer to the old
artifact name "provenance.json"; update those doc comments (on WriteResult, its
VendoredCharts field, and the other similar comment block near the end of the
file) to mention "provenance.yaml" (and update any reference to WriteProvenance
usage text accordingly) so the exported API docs correctly state the emitted
artifact name.
- Around line 127-135: The guard currently multiplies component count by 2
unconditionally and rejects bundles in vendored mode; change the condition to
only apply the 2× bound when opts.VendorCharts is false. Specifically, in the
block that checks "if 2*len(opts.Components) > 999" use a conditional that uses
len(opts.Components) > 999 when opts.VendorCharts is true, otherwise keep the
2*len(...) > 999 check; update the error path that returns WriteResult{} and
errors.New(...) accordingly so valid vendored bundles aren't rejected.

In `@pkg/defaults/timeouts.go`:
- Line 544: The comment above the Helm timeouts in pkg/defaults/timeouts.go is
misleadingly labeled "Helm SDK chart-pull"; update the comment to clarify that
these timeouts apply to the bundle-time --vendor-charts path which currently
uses CLIChartPuller (exec.CommandContext invoking `helm pull`), and optionally
note that an SDKChartPuller could be supported in the future—mention
CLIChartPuller and SDKChartPuller by name so readers know which implementation
the timeout refers to.

---

Duplicate comments:
In `@pkg/bundler/deployer/helm/helm.go`:
- Around line 99-103: The struct field comment for vendorRecords and the error
message inside generateProvenanceFile incorrectly reference "provenance.json"
even though the writer emits "provenance.yaml"; update the comment on
vendorRecords to say provenance.yaml and change the error string in
generateProvenanceFile (the log/return that says "failed to generate
provenance.json") to "failed to generate provenance.yaml" so both documentation
and runtime errors match the actual output file name.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: e56a125a-8ceb-4d38-a9fe-62e533ebade5

📥 Commits

Reviewing files that changed from the base of the PR and between 01afd79 and 62fbb16.

📒 Files selected for processing (28)
  • api/aicr/v1/server.yaml
  • docs/contributor/api-server.md
  • docs/contributor/cli.md
  • docs/user/api-reference.md
  • docs/user/cli-reference.md
  • pkg/bundler/bundler.go
  • pkg/bundler/config/config.go
  • pkg/bundler/config/config_test.go
  • pkg/bundler/deployer/argocd/argocd.go
  • pkg/bundler/deployer/argocdhelm/argocdhelm.go
  • pkg/bundler/deployer/helm/helm.go
  • pkg/bundler/deployer/localformat/provenance.go
  • pkg/bundler/deployer/localformat/provenance_test.go
  • pkg/bundler/deployer/localformat/templates/wrapper-chart.yaml.tmpl
  • pkg/bundler/deployer/localformat/vendor.go
  • pkg/bundler/deployer/localformat/vendor_folder.go
  • pkg/bundler/deployer/localformat/vendor_test.go
  • pkg/bundler/deployer/localformat/vendor_wrapper.go
  • pkg/bundler/deployer/localformat/vendor_wrapper_test.go
  • pkg/bundler/deployer/localformat/vendor_write_test.go
  • pkg/bundler/deployer/localformat/writer.go
  • pkg/bundler/deployer/localformat/writer_test.go
  • pkg/bundler/handler.go
  • pkg/cli/bundle.go
  • pkg/config/config.go
  • pkg/config/resolve.go
  • pkg/defaults/timeouts.go
  • tests/chainsaw/cli/bundle-vendor-charts/chainsaw-test.yaml

Comment thread pkg/bundler/deployer/localformat/vendor_test.go
Comment thread pkg/bundler/deployer/localformat/vendor_write_test.go Outdated
Comment thread pkg/bundler/deployer/localformat/writer.go
Comment thread pkg/bundler/deployer/localformat/writer.go
Comment thread pkg/defaults/timeouts.go Outdated
@lockwobr lockwobr force-pushed the feat/helm-vendor branch from 62fbb16 to 7ddb60e Compare May 11, 2026 22:20

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

Caution

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

⚠️ Outside diff range comments (1)
pkg/bundler/deployer/argocd/argocd.go (1)

260-266: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset vendorRecords at the start of Generate to avoid stale reads after early failures.

If Generate returns before the new assignment (e.g., early error path), VendorRecords() can expose records from a previous successful call on the same Generator instance.

🛠️ Minimal fix
 func (g *Generator) Generate(ctx context.Context, outputDir string) (*deployer.Output, error) {
 	start := time.Now()
+	g.vendorRecords = nil
 
 	output := &deployer.Output{
 		Files: make([]string, 0),
 	}

Also applies to: 315-327

🤖 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 `@pkg/bundler/deployer/argocd/argocd.go` around lines 260 - 266, Reset the
Generator's vendorRecords at the start of Generator.Generate to avoid exposing
stale data if Generate returns early: set g.vendorRecords = nil (or an empty
struct/value as appropriate) at the top of Generate before any early-returning
work so VendorRecords() cannot return previous-call data; apply the same reset
in the other Generate-like path referenced around the 315-327 section to ensure
both code paths clear g.vendorRecords before proceeding.
🤖 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 `@docs/user/api-reference.md`:
- Line 332: Update the `vendor-charts` API reference row in
docs/user/api-reference.md to append a short failure clause stating that the
request will fail if prerequisites are missing, and that it returns the same
status/code behavior as documented in server.yaml; specifically, modify the
`vendor-charts` table cell (the line mentioning provenance.yaml and Helm
binary/credentials) to add a brief phrase like "request fails (see server.yaml
for status/code)" so consumers know the observable failure mode without needing
to cross-reference.

In `@pkg/bundler/deployer/localformat/vendor_folder.go`:
- Around line 89-95: After calling puller.Pull (which returns tgz, rec, tarball)
ensure VendorRecord.TarballName is canonicalized to the actual tarball filename
written by writeVendoredHelmFolder: set rec.TarballName = tarball immediately
after the successful pull and before writing the tarball/provenance so
provenance.yaml points to the real file; apply the same fix in the other pull
site (the second pull block around the later lines) so both uses of
rec.TarballName reflect the written charts/<tarball>.

In `@pkg/cli/bundle.go`:
- Around line 402-407: The help text for the --vendor-charts flag (the Usage
string in bundle.go that describes vendoring Helm charts) overstates offline
capabilities; update that Usage text to clarify that vendoring removes Helm
chart registry egress only and does not guarantee full offline deployability of
all bundle artifacts or external dependencies, and ensure the message references
that vendoring records provenance (name, version, source URL, SHA256) but may
still require network access for other assets; edit the string associated with
--vendor-charts/Usage to reflect this narrower scope.

In `@tests/chainsaw/cli/bundle-vendor-charts/chainsaw-test.yaml`:
- Around line 47-49: The test uses a fixed WORK directory
(WORK="/tmp/chainsaw-bundle-vendor-charts") which can collide across concurrent
runs; change the setup in the test script to create a per-run unique work
directory (e.g., use mktemp -d or append $$/timestamp/UUID) and use that
variable wherever WORK is referenced (the current creation/removal block and the
other occurrences in the same file). Ensure the cleanup still removes the unique
directory and update all spots that reference WORK so they use the new per-run
path.

---

Outside diff comments:
In `@pkg/bundler/deployer/argocd/argocd.go`:
- Around line 260-266: Reset the Generator's vendorRecords at the start of
Generator.Generate to avoid exposing stale data if Generate returns early: set
g.vendorRecords = nil (or an empty struct/value as appropriate) at the top of
Generate before any early-returning work so VendorRecords() cannot return
previous-call data; apply the same reset in the other Generate-like path
referenced around the 315-327 section to ensure both code paths clear
g.vendorRecords before proceeding.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 8520456a-5bcf-4f10-9907-c7a173c9d65d

📥 Commits

Reviewing files that changed from the base of the PR and between 62fbb16 and 7ddb60e.

📒 Files selected for processing (28)
  • api/aicr/v1/server.yaml
  • docs/contributor/api-server.md
  • docs/contributor/cli.md
  • docs/user/api-reference.md
  • docs/user/cli-reference.md
  • pkg/bundler/bundler.go
  • pkg/bundler/config/config.go
  • pkg/bundler/config/config_test.go
  • pkg/bundler/deployer/argocd/argocd.go
  • pkg/bundler/deployer/argocdhelm/argocdhelm.go
  • pkg/bundler/deployer/helm/helm.go
  • pkg/bundler/deployer/localformat/provenance.go
  • pkg/bundler/deployer/localformat/provenance_test.go
  • pkg/bundler/deployer/localformat/templates/wrapper-chart.yaml.tmpl
  • pkg/bundler/deployer/localformat/vendor.go
  • pkg/bundler/deployer/localformat/vendor_folder.go
  • pkg/bundler/deployer/localformat/vendor_test.go
  • pkg/bundler/deployer/localformat/vendor_wrapper.go
  • pkg/bundler/deployer/localformat/vendor_wrapper_test.go
  • pkg/bundler/deployer/localformat/vendor_write_test.go
  • pkg/bundler/deployer/localformat/writer.go
  • pkg/bundler/deployer/localformat/writer_test.go
  • pkg/bundler/handler.go
  • pkg/cli/bundle.go
  • pkg/config/config.go
  • pkg/config/resolve.go
  • pkg/defaults/timeouts.go
  • tests/chainsaw/cli/bundle-vendor-charts/chainsaw-test.yaml

Comment thread docs/user/api-reference.md Outdated
Comment thread pkg/bundler/deployer/localformat/vendor_folder.go
Comment thread pkg/cli/bundle.go Outdated
Comment thread tests/chainsaw/cli/bundle-vendor-charts/chainsaw-test.yaml Outdated
@lockwobr lockwobr force-pushed the feat/helm-vendor branch from 7ddb60e to f011043 Compare May 11, 2026 22:32
@lockwobr lockwobr requested a review from mchmarny May 11, 2026 22:40
@lockwobr lockwobr enabled auto-merge (squash) May 11, 2026 22:42
@lockwobr lockwobr merged commit dd9e35e into main May 11, 2026
36 checks passed
@lockwobr lockwobr deleted the feat/helm-vendor branch May 11, 2026 23:33
@haarchri haarchri mentioned this pull request May 12, 2026
25 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: --vendor-charts opt-in flag for air-gap bundles

2 participants