Skip to content

feat(cli): add --set-json/--set-file for list and object bundle overrides#1162

Merged
yuanchen8911 merged 1 commit into
NVIDIA:mainfrom
yuanchen8911:feat/bundle-set-json
Jun 3, 2026
Merged

feat(cli): add --set-json/--set-file for list and object bundle overrides#1162
yuanchen8911 merged 1 commit into
NVIDIA:mainfrom
yuanchen8911:feat/bundle-set-json

Conversation

@yuanchen8911

@yuanchen8911 yuanchen8911 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds --set-json and --set-file to aicr bundle so list and object value overrides (e.g. agentgateway.allowedSourceRanges) can be set from the CLI. --set is scalar-only and silently mangles such fields.

Motivation / Context

aicr bundle --set component:path=value writes a bare string, so a list field like agentgateway.allowedSourceRanges (rendered into the Service's spec.loadBalancerSourceRanges) becomes a type-invalid scalar — the gateway can silently stay open at 0.0.0.0/0. The docs even warn "Do NOT use --set for this key." This adds the list-aware flags that mirror Helm, giving operators a clean CLI lockdown workflow without hand-editing the recipe.

Fixes: #1161
Related: #1160 (agentgateway open-exposure guardrails) and its PR #1163--set-json is the CLI mechanism operators use to scope allowedSourceRanges after that warning/check fires.

Type of Change

  • New feature (non-breaking change that adds functionality)
  • Documentation update

Component(s) Affected

  • CLI (cmd/aicr, pkg/cli)
  • Bundlers (pkg/bundler, pkg/component/*)
  • Docs/examples (docs/, examples/)

Implementation Notes

  • Flags (both repeatable, reuse --set's component:path validation):
    • --set-json component:path=<json> — inline JSON list/object/scalar
    • --set-file component:path=<file> — same value read from a JSON/YAML file
  • Merge semantics (pkg/component.ApplyTypedOverrides): object values deep-merge into any existing map at the path (matching inline componentRefs[].overrides), while lists and scalars replace. Applied last in buildComponentValues, so a typed override wins over scalar --set on the same path. Paths are applied shallowest-first (by segment count, then lexicographically), so when one override targets a parent object and another a key beneath it (e.g. comp:driver.env=<object> and comp:driver.env.HTTPS_PROXY=<value>), the deeper, more-specific path deterministically wins on shared keys regardless of flag order — Go's randomized map iteration is no longer observable.
  • Layering: typed overrides live in a parallel map[string]map[string]any on the bundler Config; the registry alias resolution (gpuoperatorgpu-operator) is shared with scalar --set via a new componentOverrideKeys helper. Overrides supplied under both a component's canonical name and a registered alias are merged (via mergeOverridesAcrossKeys), not first-match-wins, so mixed canonical/alias inputs no longer silently drop entries; the canonical name wins on a shared path. The scalar --set lookup was switched to the same merge for consistency.
  • File reads are bounded (os.Open + io.LimitReader against defaults.MaxSetFileBytes, 1 MiB), and the --set-file path is os.Stat-validated as a regular file before opening (rejecting directories, FIFOs, devices, and sockets up front so a special file fails fast instead of blocking in os.Open), and stay in the CLI layer — the shared config parser takes a decode callback, so no filesystem access leaks into the server-reachable pkg/bundler/config. The API server surface is intentionally unchanged.
  • Comma handling: set DisableSliceFlagSeparator: true on the bundle command so commas in slice-flag values are literal (required for comma-heavy JSON, and removes a latent footgun where a --set value containing a comma was silently split). Repeat-the-flag remains the documented way to add multiple values; comma-packing was never documented.

Testing

make test-coverage   # 76.5% total (threshold 75%); pkg/bundler/config 96.8%, pkg/component 80.0%
make lint            # golangci-lint 0 issues, yamllint, license headers, agents-sync, MDX-safe
make e2e             # 22/22 chainsaw tests pass (incl. cli-bundle-agentgateway-templates, cli-bundle-scheduling)

Also verified end-to-end with a locally built binary against a generated EKS/H100/dynamo recipe:

aicr bundle -r recipe.yaml --set-json 'agentgateway:allowedSourceRanges=["216.228.127.128/30","10.0.0.0/8"]'
# → agentgateway/values.yaml renders a real YAML list (not a bare string)
aicr bundle -r recipe.yaml --set-file 'agentgateway:allowedSourceRanges=ranges.yaml'   # same, from a file
aicr bundle -r recipe.yaml --set-json 'agentgateway:allowedSourceRanges=[bad json'      # clear, attributed error

New tests: pkg/bundler/config (JSON parse, deep-copy getter, last-wins), pkg/component (deep-merge / replace / no-aliasing / null-deletes-key, plus a 200-iteration determinism guard for overlapping parent/child paths), pkg/cli (flag wiring, --set-file read + size cap, non-regular-path rejection (FIFO/directory), comma-containing JSON), pkg/bundler (alias resolution, a regression test asserting mixed canonical+alias overrides are merged rather than dropped, end-to-end list rendering, and a precedence regression test asserting --set-json wins over --set on the same path through Make()).

make scan (grype) was not run: no new dependencies were introduced (uses stdlib encoding/json and the already-vendored gopkg.in/yaml.v3), so the vulnerability surface is unchanged.

Risk Assessment

  • Low — Additive flags; well-covered by unit + e2e + manual end-to-end.

⚠️ Maintainer sign-off needed on one behavior change: DisableSliceFlagSeparator: true is set on the whole bundle command, not just the new flags. It makes commas literal for all repeatable bundle slice flags (--set, --set-json, --set-file, --dynamic, --*-node-selector, --*-node-toleration, --workload-selector). This is required because urfave/cli v3's per-flag separator config is not exposed (the disable is command-scoped). The documented way to supply multiple values has always been to repeat the flag — comma-packing into one flag (e.g. --set a=1,b=2) was never documented as supported in AICR — but urfave/cli does split slice values on commas by default, so any existing CI script relying on that framework default (rather than AICR docs) to pack multiple values into one flag must switch to repeating the flag. That is the only usage affected; the change also removes a latent footgun where a --set value containing a comma was silently split. The selector/toleration parsers (ParseNodeSelectors, ParseTolerations) do not split on comma themselves, so repeat-the-flag is unaffected. e2e (22/22) is green.

Rollout notes: Backwards compatible for documented usage. The API server bundle endpoint is unchanged.

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)

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements CLI support for typed component value overrides in the aicr bundle command via --set-json and --set-file flags. The changes enable users to specify list and object overrides from the CLI, addressing the limitation of --set (scalar-only) that can silently corrupt list fields like agentgateway.allowedSourceRanges when set as bare strings. The implementation spans configuration parsing and storage with deep-copy isolation, deterministic override application with deep-merge semantics for objects and replace for lists, unified component alias resolution across scalar and typed paths in the bundler, and CLI wiring with file size limits and error handling. Documentation and recipe guidance are updated to warn against using --set for list fields and recommend the new typed-override flags.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Suggested labels

area/tests, documentation

Suggested reviewers

  • mchmarny
  • njhensley
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main feature: adding --set-json/--set-file flags for list and object bundle overrides, which is the primary objective of this changeset.
Linked Issues check ✅ Passed The implementation fully satisfies issue #1161 requirements: --set-json and --set-file flags are added with proper JSON parsing, deep-merge semantics for objects, list replacement, precedence over scalar --set, size-bounded file reads confined to CLI, and API surface unchanged.
Out of Scope Changes check ✅ Passed All changes directly support the core objectives: CLI flags, bundler config/override logic, component override application, documentation updates for the feature, and comprehensive test coverage. No unrelated changes detected.
Description check ✅ Passed The PR description comprehensively relates to the changeset, detailing the addition of --set-json and --set-file flags with clear motivation, implementation details, testing, and risk assessment.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


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

@yuanchen8911 yuanchen8911 marked this pull request as draft June 3, 2026 00:00
@yuanchen8911 yuanchen8911 changed the title feat(cli): add --set-json/--set-file for list and object bundle overrides WIP: feat(cli): add --set-json/--set-file for list and object bundle overrides Jun 3, 2026
@yuanchen8911 yuanchen8911 force-pushed the feat/bundle-set-json branch from a940158 to 9d63bd8 Compare June 3, 2026 00:13

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

🤖 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/cli-reference.md`:
- Around line 1307-1309: The fenced code block following the "**Examples:**"
heading lacks the required blank line for markdownlint MD031; insert one blank
line between the "**Examples:**" line and the opening ```shell fence so the code
block is separated from the preceding paragraph/header (update the section that
contains the "**Examples:**" heading and its subsequent fenced block to include
the blank line).
🪄 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: 4863a429-bdd6-48b4-8f8f-6187f4baee43

📥 Commits

Reviewing files that changed from the base of the PR and between a940158 and 9d63bd8.

📒 Files selected for processing (14)
  • docs/user/cli-reference.md
  • docs/user/component-catalog.md
  • pkg/bundler/bundler.go
  • pkg/bundler/bundler_test.go
  • pkg/bundler/config/config.go
  • pkg/bundler/config/typed_override.go
  • pkg/bundler/config/typed_override_test.go
  • pkg/cli/bundle.go
  • pkg/cli/bundle_config.go
  • pkg/cli/bundle_typed_overrides_test.go
  • pkg/component/overrides.go
  • pkg/component/typed_overrides_test.go
  • pkg/defaults/spec.go
  • recipes/components/agentgateway/values.yaml

Comment thread docs/user/cli-reference.md

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

🤖 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/typed_override.go`:
- Around line 96-103: In WithValueOverridesTypedPaths, reject any
TypedComponentPath entries where tp.Path == "enabled" so non-CLI callers cannot
stash an enabled: override; locate the loop in WithValueOverridesTypedPaths (and
the TypedComponentPath type) and add a guard that fails fast (e.g., panic or
return an error from the caller path if your Option pattern supports errors)
with a clear message referencing "enabled" and the component name instead of
inserting it into c.valueOverridesTyped[tp.Component][tp.Path]; this enforces
the same rejection as pkg/cli/bundle_config.go at the bundler boundary.

In `@pkg/cli/bundle_config.go`:
- Around line 135-146: The current error handling wraps parse errors from
bundlercfg.ParseTypedOverrides(..., decodeSetFileValue) and
bundlercfg.ParseValueOverridesJSON(...) with errors.Wrap, which double-wraps
structured ErrCodeInvalidRequest errors; change those returns to use
errors.PropagateOrWrap(err, errors.ErrCodeInvalidRequest, "invalid --set-file")
and errors.PropagateOrWrap(err, errors.ErrCodeInvalidRequest, "invalid
--set-json") (or simply return the original err) so that structured parse errors
are propagated intact instead of being re-wrapped.
- Around line 182-185: The current os.Open(path) error is always wrapped as
errors.ErrCodeNotFound; change the handling to check os.IsNotExist(err) first
and only map that branch to errors.ErrCodeNotFound, and for all other errors
wrap them with an appropriate fallback code (e.g., errors.ErrCodeIO or a
suitable existing I/O/error code) using errors.Wrap; update the os.Open(path)
error path where errors.Wrap is called so it uses os.IsNotExist(err) to choose
between errors.ErrCodeNotFound and the fallback code.
🪄 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: b442c0d2-f72e-4821-a61f-3547db71112a

📥 Commits

Reviewing files that changed from the base of the PR and between 61ee797 and 541e47d.

📒 Files selected for processing (16)
  • docs/user/cli-reference.md
  • docs/user/component-catalog.md
  • pkg/bundler/bundler.go
  • pkg/bundler/bundler_test.go
  • pkg/bundler/config/config.go
  • pkg/bundler/config/typed_override.go
  • pkg/bundler/config/typed_override_test.go
  • pkg/cli/bundle.go
  • pkg/cli/bundle_config.go
  • pkg/cli/bundle_typed_overrides_test.go
  • pkg/component/overrides.go
  • pkg/component/typed_overrides_test.go
  • pkg/defaults/spec.go
  • pkg/serializer/copy.go
  • pkg/serializer/copy_test.go
  • recipes/components/agentgateway/values.yaml

Comment thread pkg/bundler/config/typed_override.go
Comment thread pkg/cli/bundle_config.go
Comment thread pkg/cli/bundle_config.go
@yuanchen8911 yuanchen8911 force-pushed the feat/bundle-set-json branch from 541e47d to 38a8ac5 Compare June 3, 2026 03:27
@yuanchen8911 yuanchen8911 changed the title WIP: feat(cli): add --set-json/--set-file for list and object bundle overrides feat(cli): add --set-json/--set-file for list and object bundle overrides Jun 3, 2026
@yuanchen8911 yuanchen8911 marked this pull request as ready for review June 3, 2026 03:32
@yuanchen8911 yuanchen8911 force-pushed the feat/bundle-set-json branch from 38a8ac5 to f7b7c43 Compare June 3, 2026 03:36

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

🤖 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/cli-reference.md`:
- Line 1342: The heading "Replace an object value (deep-merges into the existing
map)" is contradictory; update the heading text to reflect merge semantics
(e.g., "Update an object value (deep-merges into the existing map)" or "Merge
into an object value (deep-merges into the existing map)") so it no longer
implies replace semantics while preserving the parenthetical note; edit the
heading line in docs/user/cli-reference.md where that exact phrase appears.

In `@pkg/cli/bundle.go`:
- Around line 461-468: Update the --set-json flag help text (the
cli.StringSliceFlag with Name "set-json") to explicitly state that JSON scalars
(booleans, numbers, null) are supported in addition to lists/objects; modify the
Usage string to mention scalar acceptance, adjust the example or add a short
scalar example (e.g., --set-json mycomp:replicas=3 or mycomp:enabled=true) and
clarify that scalars replace existing values while objects deep-merge and lists
replace.

In `@pkg/component/overrides.go`:
- Around line 122-150: The mergeAnyMap implementation currently copies a src map
verbatim when the destination key isn't a map, which preserves nil children as
explicit nulls instead of treating them as "delete" markers; update mergeAnyMap
so that in the branch where you assign dst[k] = serializer.DeepCopyAnyMap(svMap)
you instead iterate svMap and for each entry set dstSub[key] =
DeepCopyAny(value) for non-nil values and delete(dstSub, key) for nil values
(creating dstSub as a new map if needed), so nil children are interpreted as
deletions rather than stored as nulls; refer to mergeAnyMap, svMap, dst and
dst[k] to locate where to change the assignment.
🪄 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: 61e87f47-e075-41ff-bbbb-39ab5172451b

📥 Commits

Reviewing files that changed from the base of the PR and between 541e47d and 38a8ac5.

📒 Files selected for processing (16)
  • docs/user/cli-reference.md
  • docs/user/component-catalog.md
  • pkg/bundler/bundler.go
  • pkg/bundler/bundler_test.go
  • pkg/bundler/config/config.go
  • pkg/bundler/config/typed_override.go
  • pkg/bundler/config/typed_override_test.go
  • pkg/cli/bundle.go
  • pkg/cli/bundle_config.go
  • pkg/cli/bundle_typed_overrides_test.go
  • pkg/component/overrides.go
  • pkg/component/typed_overrides_test.go
  • pkg/defaults/spec.go
  • pkg/serializer/copy.go
  • pkg/serializer/copy_test.go
  • recipes/components/agentgateway/values.yaml

Comment thread docs/user/cli-reference.md Outdated
Comment thread pkg/cli/bundle.go
Comment thread pkg/component/overrides.go Outdated
@yuanchen8911 yuanchen8911 force-pushed the feat/bundle-set-json branch 4 times, most recently from 9121ea5 to 81a5353 Compare June 3, 2026 04:20
@yuanchen8911 yuanchen8911 requested a review from mchmarny June 3, 2026 04:23
@yuanchen8911 yuanchen8911 force-pushed the feat/bundle-set-json branch from 81a5353 to 0f75362 Compare June 3, 2026 14:28
@yuanchen8911

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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 `@pkg/cli/bundle_config.go`:
- Around line 117-175: Move the business rules out of pkg/cli: keep
file-decoding helper (decodeSetFileValue) and raw flag parsing calls (e.g.,
cmd.StringSlice in resolveTypedOverrides) in pkg/cli but relocate precedence
handling (collecting --set-file before --set-json and merge ordering) and the
top-level enabled-path validation (logic that checks tp.Path ==
componentEnabledKey) into pkg/bundler/config (or bundler) so the bundler package
owns the contract; have resolveTypedOverrides call into a new bundlercfg
function that accepts the parsed TypedComponentPath slices (from
bundlercfg.ParseTypedOverrides and bundlercfg.ParseValueOverridesJSON) and
returns the final merged []bundlercfg.TypedComponentPath or an error, preserving
existing error propagation from ParseTypedOverrides/ParseValueOverridesJSON.
- Around line 189-211: In decodeSetFileValue, reject non-regular paths before
calling os.Open: call os.Lstat (or os.Stat) on path first and if it returns
os.ErrNotExist wrap and return errors.ErrCodeNotFound, otherwise if the file
mode is not a regular file (or IsDir) return errors.ErrCodeInvalidRequest with a
message that the --set-file path is not a regular file (or is a directory). Only
proceed to os.Open after confirming the path exists and is a regular file. Keep
existing error codes/messages for NotFound and InvalidRequest to match current
behavior.
🪄 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: 2bbcdb69-6489-48f2-889d-0d59bc3b1f19

📥 Commits

Reviewing files that changed from the base of the PR and between 541e47d and 0f75362.

📒 Files selected for processing (14)
  • docs/user/cli-reference.md
  • docs/user/component-catalog.md
  • pkg/bundler/bundler.go
  • pkg/bundler/bundler_test.go
  • pkg/bundler/config/config.go
  • pkg/bundler/config/typed_override.go
  • pkg/bundler/config/typed_override_test.go
  • pkg/cli/bundle.go
  • pkg/cli/bundle_config.go
  • pkg/cli/bundle_typed_overrides_test.go
  • pkg/component/overrides.go
  • pkg/component/typed_overrides_test.go
  • pkg/defaults/spec.go
  • recipes/components/agentgateway/values.yaml

Comment thread pkg/cli/bundle_config.go
Comment on lines +117 to +175
// resolveTypedOverrides parses the --set-json and --set-file flags into
// structured component-path overrides for list/object values that scalar
// --set cannot express (see #1161). --set-json decodes the value as JSON;
// --set-file reads the value from a file and decodes it as YAML (a JSON
// superset). Both reuse --set's "component:path" validation.
//
// The two flags are additive. When both target the same component:path,
// --set-file is collected first so an inline --set-json wins on that path,
// mirroring Helm's precedence of --set over -f value files. (urfave/cli does
// not expose the interleaved command-line order across two distinct flags, so
// this fixed precedence is the deterministic contract rather than raw argument
// order.) Within a single flag, the last entry for a given component:path
// wins. Returns nil when neither flag is set.
func resolveTypedOverrides(cmd *cli.Command) ([]bundlercfg.TypedComponentPath, error) {
var out []bundlercfg.TypedComponentPath

// Collect --set-file first so a later-applied --set-json on the same
// component:path takes precedence (Helm-like: --set beats -f).
//
// ParseTypedOverrides / ParseValueOverridesJSON already return structured
// ErrCodeInvalidRequest errors that name the offending spec (and, for value
// decode/format failures, the flag), so propagate them as-is rather than
// re-wrapping with the same code.
if cmd.IsSet("set-file") {
parsed, err := bundlercfg.ParseTypedOverrides(cmd.StringSlice("set-file"), "--set-file", decodeSetFileValue)
if err != nil {
return nil, err
}
out = append(out, parsed...)
}

if cmd.IsSet("set-json") {
parsed, err := bundlercfg.ParseValueOverridesJSON(cmd.StringSlice("set-json"))
if err != nil {
return nil, err
}
out = append(out, parsed...)
}

// The top-level "enabled" key is the bundler's component include/exclude
// toggle, not a Helm chart value — it is honored only via scalar --set
// (getSetEnabledOverride) and stripped before reaching chart values.
// Routing it through the typed flags would neither toggle the component nor
// strip the key: it would silently write a stray literal `enabled:` into the
// component's values and ship a component the operator believed disabled.
// Reject it loudly with a pointer to --set rather than emit a misconfigured
// bundle. A nested chart value such as `gds.enabled` is unaffected — only
// the exact toggle path is blocked.
for _, tp := range out {
if tp.Path == componentEnabledKey {
return nil, errors.New(errors.ErrCodeInvalidRequest,
fmt.Sprintf("%q is the component enable/disable toggle and must be set with --set "+
"(e.g. --set %s:%s=false), not --set-json/--set-file",
componentEnabledKey, tp.Component, componentEnabledKey))
}
}

return out, nil
}

@coderabbitai coderabbitai Bot Jun 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Move typed-override precedence and enabled validation out of pkg/cli.

This function now owns domain rules that the bundler also enforces, so CLI and non-CLI entry points can drift independently. Keep the file-decoding callback in pkg/cli, but move the precedence/validation contract into pkg/bundler/config (or the bundler) so the CLI stays as thin wiring.

Based on learnings: "In this repo, ensure packages under pkg/cli contain only CLI wiring (command setup, flag/urfave/cli plumbing, and user-interaction helpers like TTY confirmation). Avoid implementing business logic in pkg/cli."

🤖 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/cli/bundle_config.go` around lines 117 - 175, Move the business rules out
of pkg/cli: keep file-decoding helper (decodeSetFileValue) and raw flag parsing
calls (e.g., cmd.StringSlice in resolveTypedOverrides) in pkg/cli but relocate
precedence handling (collecting --set-file before --set-json and merge ordering)
and the top-level enabled-path validation (logic that checks tp.Path ==
componentEnabledKey) into pkg/bundler/config (or bundler) so the bundler package
owns the contract; have resolveTypedOverrides call into a new bundlercfg
function that accepts the parsed TypedComponentPath slices (from
bundlercfg.ParseTypedOverrides and bundlercfg.ParseValueOverridesJSON) and
returns the final merged []bundlercfg.TypedComponentPath or an error, preserving
existing error propagation from ParseTypedOverrides/ParseValueOverridesJSON.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion. We will keep the precedence and enabled-toggle handling in resolveTypedOverrides for now rather than relocate it. The actual contract already lives below the CLI boundary: the bundler independently rejects the enabled toggle on typed overrides at pkg/bundler/bundler.go (the fix from the previous round), so non-CLI callers fail closed regardless of this function. What remains in pkg/cli is (a) a fast, flag-specific UX rejection that points the operator at --set, and (b) the --set-file-before---set-json precedence, which is inherently a CLI flag-ordering concern (urfave/cli does not expose interleaved order across two distinct flags). Moving these into pkg/bundler/config is a reasonable longer-term cleanup but a heavier refactor than this PR warrants given the boundary guard already exists, so we will leave it as a possible follow-up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@yuanchen8911, that's a well-reasoned call. The belt-and-suspenders approach — bundler rejects enabled on typed overrides regardless of entry point, CLI adds a fast, user-friendly rejection with a pointer to --set — is a solid pattern, and you're right that the --set-file/--set-json precedence is inherently a CLI flag-ordering concern that has no meaning below the CLI boundary. Happy to leave it as a possible follow-up.

🐇 ~(っ^▿^)っ


✏️ Learnings added
Learnt from: yuanchen8911
Repo: NVIDIA/aicr PR: 1162
File: pkg/cli/bundle_config.go:117-175
Timestamp: 2026-06-03T15:57:09.826Z
Learning: In NVIDIA/aicr, `pkg/cli/bundle_config.go`'s `resolveTypedOverrides` intentionally keeps two concerns in the CLI layer: (1) a fast UX early-rejection of the `enabled` component toggle (pointing the operator to `--set`), and (2) `--set-file`-before-`--set-json` precedence ordering (because urfave/cli does not expose interleaved order across two distinct flags). The actual contract (rejecting typed overrides targeting `enabled`) is separately enforced as a boundary guard in `pkg/bundler/bundler.go`, so non-CLI callers fail closed regardless. Do not flag the CLI-layer duplication as a violation of the "thin CLI" principle; the dual-layer defense is intentional per PR `#1162`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: njhensley
Repo: NVIDIA/aicr PR: 595
File: pkg/recipe/loader.go:56-60
Timestamp: 2026-04-16T18:20:53.204Z
Learning: In this repository’s Go code, if an error was already created/wrapped with `pkg/errors` (via `aicrerrors.New(...)` or `aicrerrors.WrapWithContext(...)`) and includes the correct structured error code (e.g., `ErrCodeInvalidRequest`, `ErrCodeTimeout`, `ErrCodeInternal`), then returning it should use the bare form `return nil, err` (or propagate the error as-is). Do not require “missing context” when the source already has proper codes; do not double-wrap it. Avoid changing the error by using `errors.Wrapf` (which can override the structured code) or `fmt.Errorf("%w", err)` in these cases, which is documented as an anti-pattern in `CLAUDE.md`.

Learnt from: yuanchen8911
Repo: NVIDIA/aicr PR: 691
File: pkg/cli/skill.go:47-75
Timestamp: 2026-04-27T04:46:14.966Z
Learning: In this repo, ensure packages under `pkg/cli` contain only CLI wiring (command setup, flag/urfave/cli plumbing, and user-interaction helpers like TTY confirmation). Avoid implementing business logic in `pkg/cli`: interfaces and generator/meta extraction logic (e.g., `skillGenerator`, `claudeCodeGenerator`/`codexGenerator`, `cliMeta`, `extractCLIMeta`) should be moved into a dedicated package such as `pkg/skill` (or an appropriate domain package) so `pkg/cli` is limited to `cli.Command` orchestration (e.g., `skillCmdFlags`/`skillCmd`).

Learnt from: mchmarny
Repo: NVIDIA/aicr PR: 728
File: pkg/recipe/criteria.go:801-820
Timestamp: 2026-05-01T23:59:30.754Z
Learning: In NVIDIA/aicr, use the idiomatic helper `errors.PropagateOrWrap(err, errCode, message)` from `pkg/errors` when handling errors in Go code. This helper should be preferred over manual `errors.As` + conditional return logic: it propagates `err` as-is if it already carries a `*StructuredError` code, and otherwise wraps `err` using the provided fallback `errCode` and `message`. Apply this pattern especially when calling functions from `pkg/serializer`, `pkg/recipe`, or other packages that may already return coded structured errors.

Learnt from: njhensley
Repo: NVIDIA/aicr PR: 844
File: pkg/fingerprint/from_measurements.go:200-220
Timestamp: 2026-05-11T21:09:20.802Z
Learning: In this repo’s Go code, do not flag or request changes for `int64`→`int` casts when the value originates from a Go `int` and remains in-process (e.g., it round-trips via `measurement.Reading.Any()` but never crosses a system boundary like serialization/network/DB). Only validate/guard numeric ranges at system boundaries; adding `math.MaxInt32/MinInt32` clamping for such internal-only casts is an anti-pattern (per CLAUDE.md), since the scenario is guaranteed by internal/framework behavior and 64-bit Linux targets keep `int` effectively 64 bits.

Learnt from: mchmarny
Repo: NVIDIA/aicr PR: 1126
File: pkg/api/doc.go:105-105
Timestamp: 2026-05-30T16:28:11.381Z
Learning: In NVIDIA/aicr, timeout deadlines are intentionally layered: `defaults.ServerHandlerTimeout` (90s) is the outer HTTP/server ceiling, while individual handlers may set tighter per-operation deadlines using `context.WithTimeout` (e.g., 30s) to bound specific I/O or sub-operations. During code review, do not flag this as a conflict or redundancy: documenting both values is correct and intended. The outer timeout prevents net/http from closing connections mid-handler, and the inner deadlines constrain only the relevant sub-operation(s).

Comment thread pkg/cli/bundle_config.go
@yuanchen8911 yuanchen8911 force-pushed the feat/bundle-set-json branch from 0f75362 to 3f228a9 Compare June 3, 2026 15:56
mchmarny
mchmarny previously approved these changes Jun 3, 2026

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

Approving. Clean implementation with strong test coverage (incl. a 200-run determinism guard against map-iteration nondeterminism), defense-in-depth on the enabled toggle (rejected at both CLI and bundler), aliasing properly prevented on insert + getter, and a careful stat-then-open + LimitReader flow for --set-file that handles FIFOs/directories up front. CI green across 116 checks.

Three non-blocking notes inline, all medium-or-below:

  • DisableSliceFlagSeparator: true silently changes comma-handling for all existing slice flags on bundle, not just the new ones — worth a callout in the PR description for operators with CI scripts.
  • The new alias-merge behavior (a real bug fix) applies to scalar --set too, but is documented only under the typed-flag section.
  • computeSchedulingPathPolicy suppression considers only scalar --set, not typed overrides — asymmetry is probably intentional but undocumented.

None of these block merge.

Comment thread pkg/cli/bundle.go
Comment thread pkg/bundler/bundler.go
Comment thread pkg/bundler/bundler.go
@mchmarny mchmarny self-requested a review June 3, 2026 16:13
…ides

`aicr bundle --set` is scalar-only, so it cannot express list/object value
overrides. Pointing it at a list field such as agentgateway.allowedSourceRanges
exits 0 but writes a bare string into spec.loadBalancerSourceRanges, producing a
type-invalid Service that may silently stay open at 0.0.0.0/0.

Add two list-aware flags that mirror Helm's:

- --set-json component:path=<json>  — inline JSON list/object/scalar
- --set-file component:path=<file>  — same, read from a JSON/YAML file

Both reuse --set's component:path validation. Values are applied last (highest
precedence): object values deep-merge into existing maps (matching inline
componentRef overrides), while lists and scalars replace. File reads are bounded
to defaults.MaxSetFileBytes via os.Open + io.LimitReader, and file I/O stays in
the CLI layer (the shared config parser takes a decode callback), so no
filesystem access leaks into the server-reachable config package.

Docs: cli-reference gains a "List and Object Value Overrides" section; the
agentgateway value comment and component-catalog now point at --set-json as the
clean CLI lockdown workflow.

Closes NVIDIA#1161
@yuanchen8911 yuanchen8911 enabled auto-merge (squash) June 3, 2026 16:43
@yuanchen8911 yuanchen8911 merged commit f0490fb into NVIDIA:main Jun 3, 2026
118 checks passed
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.

bundle: support --set-json / --set-file for list and object value overrides

2 participants