feat(bom): render divergent recipe version pins as BOM variants#1634
Conversation
|
🌿 Preview your docs: https://nvidia-preview-feat-bom-variant-rows.docs.buildwithfern.com/aicr |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds recipe-derived BOM variants for divergent Helm pins, rendering variant images through CycloneDX and Markdown outputs. It adds checked BOM reference validation, render-fidelity metadata, strict recipe-source loading, bidirectional freshness tests, CI execution checks, variant-focused test coverage, and regenerated container image documentation. Estimated code review effort: 4 (Complex) | ~75 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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/bom/bom.go`:
- Around line 114-121: Add a regression assertion for the serialized CycloneDX
output produced by BuildBOM, not just the markdown and duplicate-count checks in
TestLegacyEntryPointsUnchanged. Use BuildBOM and the encoded BOM serialization
path to compare the full byte-stable output, verifying component/dependency
ordering and image dedup remain unchanged for the legacy entry point.
In `@tools/bom/freshness_test.go`:
- Around line 465-486: In parseBOMVariantsTable, the generic blank-row skip
currently allows all-empty table rows like | | | | | to be ignored before
validation, so remove that early continue and let the existing empty-cell checks
reject malformed rows. Update the row handling around expectSeparator/cell
validation to ensure blank version rows surface an error, and add a
TestParseBOMVariantsTable case covering the all-blank row to lock in the
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: 2dadc471-b53d-442a-ae1e-512100a4ff33
📒 Files selected for processing (14)
.github/workflows/merge-gate.yamldocs/contributor/recipe.mddocs/user/container-images.mdpkg/bom/bom.gopkg/bom/markdown.gopkg/bom/variants.gopkg/bom/variants_test.gotools/bom/freshness_parse_test.gotools/bom/freshness_test.gotools/bom/main.gotools/bom/main_test.gotools/bom/variants.gotools/bom/variants_test.gotools/bom/variants_unix_test.go
df55122 to
287705f
Compare
287705f to
bfac8fb
Compare
There was a problem hiding this comment.
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 `@tools/bom/variants.go`:
- Around line 264-315: Make deriveVariants cancellation-aware by adding the
existing context parameter and checking ctx.Done() while iterating over sources
and refs, returning ctx.Err() when cancelled. Update its caller(s), including
the relevant command flow, to pass the context through without changing variant
aggregation 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: 4be67163-ff6f-456f-8d51-4f9608df63fe
📒 Files selected for processing (14)
.github/workflows/merge-gate.yamldocs/contributor/recipe.mddocs/user/container-images.mdpkg/bom/bom.gopkg/bom/markdown.gopkg/bom/variants.gopkg/bom/variants_test.gotools/bom/freshness_parse_test.gotools/bom/freshness_test.gotools/bom/main.gotools/bom/main_test.gotools/bom/variants.gotools/bom/variants_test.gotools/bom/variants_unix_test.go
bfac8fb to
08621e3
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
tools/bom/variants.go (1)
264-341: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
deriveVariantsstill isn't cancellation-aware.This traverses arbitrary
-repo-rootdeclarations (potentially large/nested overlay trees) but has nocontext.Contextparameter and never checks for cancellation, unlikeloadRecipeSources/walkYAMLwhich already do. This was flagged in a previous review round and the fix was not applied to this function.🔧 Proposed fix
-func deriveVariants(reg *registry, sources []recipeSource) ([]bom.VariantResult, error) { +func deriveVariants(ctx context.Context, reg *registry, sources []recipeSource) ([]bom.VariantResult, error) { byName := make(map[string]component, len(reg.Components)) for _, c := range reg.Components { byName[c.Name] = c } agg := map[variantKey]map[string]struct{}{} for _, src := range sources { for _, ref := range src.Refs { + if ctxErr := ctx.Err(); ctxErr != nil { + return nil, errors.Wrap(errors.ErrCodeTimeout, "deriving BOM variants canceled", ctxErr) + } pin := ref.VersionAlso update the caller in
tools/bom/main.go(deriveVariants(reg, sources)→deriveVariants(ctx, reg, sources)) and the test call sites intools/bom/variants_test.go.🤖 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 `@tools/bom/variants.go` around lines 264 - 341, Make deriveVariants cancellation-aware by adding a context.Context parameter, checking ctx.Err() while traversing sources and refs, and returning the cancellation error promptly. Update its caller in main and every test invocation to pass the existing context, preserving the current aggregation behavior.Source: Coding guidelines
🤖 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/bom/bom.go`:
- Around line 312-369: Extract the shared Helm property construction from
componentProperties and variantComponent into a helper that assembles
aicr:helm:repository, aicr:helm:chart, aicr:helm:version, and
aicr:helm:namespace using the existing component fields and omission rules.
Update both functions to call the helper, while keeping componentProperties’
Kustomize-specific property names and behavior intact.
In `@tools/bom/freshness_test.go`:
- Around line 402-490: Ensure parseBOMVariantsTable validates expectSeparator
before handling any non-table line and after the scan ends, returning the
existing invalid-delimiter error when a matched header is not immediately
followed by a valid delimiter row; do not silently reset
inTable/expectSeparator. Update the loop logic and add an end-of-function guard
so a header followed by blank/prose/heading or end-of-input is rejected.
---
Duplicate comments:
In `@tools/bom/variants.go`:
- Around line 264-341: Make deriveVariants cancellation-aware by adding a
context.Context parameter, checking ctx.Err() while traversing sources and refs,
and returning the cancellation error promptly. Update its caller in main and
every test invocation to pass the existing context, preserving the current
aggregation 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: c124cb74-b06f-4849-9f90-1566e0194eb3
📒 Files selected for processing (14)
.github/workflows/merge-gate.yamldocs/contributor/recipe.mddocs/user/container-images.mdpkg/bom/bom.gopkg/bom/markdown.gopkg/bom/variants.gopkg/bom/variants_test.gotools/bom/freshness_parse_test.gotools/bom/freshness_test.gotools/bom/main.gotools/bom/main_test.gotools/bom/variants.gotools/bom/variants_test.gotools/bom/variants_unix_test.go
mchmarny
left a comment
There was a problem hiding this comment.
Solid design and execution. The declaration-level scope (pins vs registry defaults, no effective-recipe resolution) is the right cut for #1611, and the implementation holds its fail-closed promises: strict mixin decode matching the store's KnownFields semantics, duplicate-source and padded-pin rejection, os.Root-confined size-bounded reads with the O_NONBLOCK/fstat trick against blocking FIFOs, and a checked bom-ref identity set so variants can't produce an ambiguous CycloneDX graph. I verified the .yaml-only filter matches the store's, surveyVariant's version swap is safe (Helm is a value struct, no registry aliasing), and the bidirectional freshness gate plus the merge-gate PASS-line assertion for both tests closes the rename-to-no-op hole. The variants-table parser is appropriately paranoid and well pinned by TestParseBOMVariantsTable.
Two inline comments, both low severity (a scan-scope assumption vs the store's full-tree walk that deserves an explicit doc note, and a minor fail-closed inconsistency for direct in-tree FIFOs) — neither blocks merge. Core CI (Test, Lint, bom-freshness, CodeQL) is green; E2E was still running at review time.
Issue NVIDIA#1611: a base/overlay/mixin that pins a Helm component to a version different from the registry default deploys images the container BOM never mentioned. This adds declaration-level variant discovery to tools/bom and variant-aware entry points to pkg/bom, so every explicitly pinned divergent chart version appears in the BOM alongside the registry default. - tools/bom: loadRecipeSources reads every overlay/mixin under the same -repo-root recursively (os.Root-confined, size-bounded reads, canonical loader kind parity: mixins hard-require RecipeMixin, overlays accept legacy empty kind and skip foreign kinds); malformed sources, duplicate source names, and whitespace-padded pins fail closed. deriveVariants compares each explicit Helm componentRef pin against the registry defaultVersion and aggregates divergences by (component, version). Variants render through the same surveyComponent path at the variant version (catalog parity: shared per-component values, registry chart coordinates). - pkg/bom: additive BuildBOMWithVariants/WriteMarkdownWithVariants. Variant entries get version-qualified bom-refs ("<name>/<comp>@<ver>") with aicr:variant:of/aicr:variant:sources properties; the checked builder fails closed on any duplicate bom-ref. Markdown gains a "Version variants" table and per-variant image sections (omitted when no variants exist) plus a caller-supplied rendering-fidelity note. Legacy BuildBOM and WriteMarkdown are byte-identical to before (pinned by test). - Freshness: TestCommittedBOMVariantsMatchRecipePins gates the committed variants table bidirectionally (new divergent pin without a row fails; stale row without a backing pin fails); the merge-gate bom-freshness job runs and positively asserts both freshness tests. Non-goals, per the revised NVIDIA#1611: effective-recipe resolution (inheritance/composition), source/chart coordinate divergence, and componentRef type analysis beyond "is this declared pin a Helm pin" are outside the variants model; variants render at registry coordinates by definition. Fixes NVIDIA#1611 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Add a CycloneDX serialization equivalence assertion to TestLegacyEntryPointsUnchanged so BuildBOM and BuildBOMWithVariants(nil) stay byte-stable across component/dependency ordering and image dedup. Reject all-blank Version-variants data rows in the freshness parser's gate instead of silently skipping them, with a covering test case. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
parseBOMVariantsTable set expectSeparator when a header matched but only validated the delimiter row when the following line began with a pipe. A header followed by a blank line, prose, a new heading, or end-of-input took the non-pipe branch (or ended the loop) and silently reset table state, counting the table as present-but-empty with no error — violating the parser's own "malformed rows are errors, not skips" contract. Fail closed in both spots: reject a matched header not immediately followed by a valid delimiter row, and guard the end-of-section case. Add parser test cases for a header followed by prose and a header as the last line of the section. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Add a scope note on sourceDirs explaining that the BOM variant loader intentionally scans only overlays/ and mixins/, diverging from the canonical metadata store (pkg/recipe/metadata_store.go) which walks the whole recipes/ tree. By convention every overlay lives under overlays/, so the two scopes agree today; the note flags that a future layout change placing RecipeMetadata elsewhere would require widening this list to avoid dropping a divergent pin. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
103aa96 to
0bfb9f8
Compare
…IA#1634) Signed-off-by: Yuan Chen <yuanchen97@gmail.com> Co-authored-by: Mark Chmarny <mchmarny@users.noreply.github.com>
Summary
The container BOM (
docs/user/container-images.mdand the CycloneDX output oftools/bom) now lists version variants: every Helm chart version explicitly pinned by a base/overlay/mixin that differs from the component's registry default, with the images that version actually ships.Motivation / Context
A recipe source that pins a component to a non-default chart version deploys images the BOM never mentioned — the doc only rendered each component at its registry
defaultVersion. Issue #1611 (revised) scopes the fix to declaration-level discovery: derive variants directly from the explicitcomponentRefs[].versionpins versus the registry defaults, with no shared exemption file and no effective-recipe resolution. Today that surfaces one variant:kube-prometheus-stack@83.7.0, pinned byaks.Fixes: #1611
Related: #1615, #1616
Non-goals (documented in
tools/bom/variants.go, per the revised #1611):Type of Change
Component(s) Affected
docs/,examples/)pkg/bom,tools/bom,.github/workflows/merge-gate.yamlImplementation Notes
Discovery (
tools/bom/variants.go):loadRecipeSourcesreads every overlay/mixin YAML under the same-repo-rootrecursively (nested overlay subdirectories are documented and supported), parsing into the canonicalpkg/recipetypes with the metadata store's exact semantics: mixins decode strictly (KnownFields(true), hardRecipeMixinkind check — a typo'd field fails closed just as it does at recipe load); overlays decode leniently exactly as the store does (empty kind is legacyRecipeMetadata, any other kind is skipped and never mined for pins — a field the resolver would ignore is equally invisible here, so the projection matches what deploys).metadata.name, duplicate source names, and whitespace-padded version pins are errors, never silent skips — a silently dropped source could hide a divergent pin.os.Root(an escaping symlink is rejected, not followed) and size-bounded; sources are openedO_NONBLOCKand validated regular viafstaton the opened handle, so a non-regular target (e.g. a symlinked FIFO, whose open would otherwise block until a writer appears) fails closed with no stat-to-open race window; the walk checksctxper entry.deriveVariantscompares each explicit Helm pin against the registrydefaultVersionand aggregates divergences by(component, version)with the sorted declaring source names. An explicit pin over an empty registry default still renders as a variant (external-repo-rootregistries may omitdefaultVersion). The version-pin guard'sversionPinExemptionsplays no role: it is validation policy (whether a divergence is allowed), not deployment fact.surveyComponentpath as default entries, at the variant version — so variant image sets are rendered, never copied, and mixed Helm+manifest components contribute their manifest images too.Rendering (
pkg/bom):BuildBOMWithVariants/WriteMarkdownWithVariants; the legacyBuildBOM/WriteMarkdownsurfaces are byte-identical to main (pinned byTestLegacyEntryPointsUnchanged), so external importers see no behavioral change.pkg/evidence/attestationis untouched.<meta.Name>/<comp>@<ver>) withaicr:variant:ofand sortedaicr:variant:sourcesproperties; variant images join the dependency graph. The checked builder fails closed on any duplicate bom-ref instead of emitting an ambiguous graph.## Version variantstable (distinct headers, so the Components table and its existing freshness gate are unaffected) and per-variant image sections; the section is omitted entirely when no variants exist. A caller-supplied rendering-fidelity note (Metadata.RenderFidelity) documents the catalog-parity limitation: charts are rendered with the sharedrecipes/components/<name>/values.yaml, per-recipe overlay overrides are not applied.Freshness gate:
TestCommittedBOMVariantsMatchRecipePinsgates the committed variants table bidirectionally: a new divergent pin without a variant row fails, and a stale row without a backing pin fails. The table parser is strict (heading-anchored, delimiter-validated, malformed rows are errors) so a mangled doc cannot pass vacuously.bom-freshnessmerge-gate job runs both freshness tests and positively asserts a PASS line for each, so a rename or skip cannot silently drop either gate.Testing
origin/main:pkg/bom95.5% → 95.8% (+0.3%),tools/bom79.5% → 81.8% (+2.3%).WriteMarkdown≡WriteMarkdownWithVariants(nil)byte-for-byte; legacyBuildBOMkeeps its historical duplicate tolerance. All pre-existingpkg/bomandtools/bomtests pass unchanged.Risk Assessment
Rollout notes: Additive only. The legacy
pkg/bomGo API and its output are byte-identical;tools/bomoutput changes are confined to the new variants table, the fidelity note, and variant entries in the CycloneDX document. Reverting restores the previous BOM shape with no migration.Checklist
make testwith-race)make lint)git commit -S) — GPG signing info