feat(bom): add CycloneDX 1.6 image BOM generator#747
Merged
Conversation
Adds `make bom` plus a reusable `pkg/bom` package that produces an authoritative inventory of every container image AICR can deploy by rendering each Helm chart in `recipes/registry.yaml` at its pinned version, walking embedded manifests under `recipes/components/*/manifests/`, and emitting: - `bom.cdx.json` — CycloneDX 1.6 JSON (canonical, machine-readable) - `bom.md` — human-readable Markdown summary Modeling: AICR is the root component; each registry entry is an `application` component; each unique image is a `container` component with an OCI `purl`; `dependencies[]` wires the deployment graph. Output is consumable by Trivy, Grype, and Cosign attestation without conversion. Closes #740. Refs #739. Image extraction handles Helm-templated manifests by stripping `{{ ... }}` directives before YAML parsing — control-flow-only lines are dropped, inline directives become a placeholder, so static `image:` values still surface from mixed YAML/Helm files like the network-operator and kubeflow-trainer manifests. `pkg/bom` is structured for reuse: a follow-up will plumb it into `pkg/bundler` so `aicr bundle` emits a per-bundle CycloneDX SBOM alongside the generated install scripts (planned to be on by default, no flag required). Tested against the live registry: 22 components, 71 unique images across 11 registries (NGC, ghcr.io, gcr.io, ECR, quay.io, registry.k8s.io, cr.kgateway.dev, Docker Hub).
This comment was marked as resolved.
This comment was marked as resolved.
Contributor
Coverage Report ✅
Coverage BadgeMerging this branch will increase overall coverage
Coverage by fileChanged files (no unit tests)
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. |
- Replace custom newUUIDv4 with google/uuid (already in go.mod) so BOM
serial numbers are RFC 4122-conformant without hand-rolled crypto.
- Use pkg/errors with structured error codes throughout pkg/bom and
tools/bom, replacing fmt.Errorf wraps to match project convention.
- Fix OCI purl spec compliance: repository_url now includes the full
artifact path (registry/namespace/name) instead of stopping at the
namespace; when both digest and tag are present, tag moves to the
qualifier per spec. Tag-as-version remains the pragmatic fallback for
the common no-digest case.
- BuildBOM and WriteMarkdown now copy the input slice before sorting so
callers (notably the planned pkg/bundler integration) don't observe
their input reordered.
- Replace boolStr helper with strconv.FormatBool.
- Lift helm-render ctx into renderHelmComponent so the WithTimeout
cancel runs at end of helm work, not at end of the whole survey.
- Extract goconst-tripping defaults ("aicr", "NVIDIA Corporation") to
named constants.
- Drop misleading "or empty string" docstring on componentValuesPath /
componentManifestsDir; the path-joiner role is documented and callers
stat-check before using.
PURL test updated to cover the spec-correct repository_url shape and
the digest+tag qualifier case.
Recurse into n.Alias for AliasNode so an `image:` value reached via a YAML anchor + alias is still surveyed. Rare in K8s manifests but cheap to handle and now covered by a regression test.
lalitadithya
previously approved these changes
May 5, 2026
… Hub refs Two correctness fixes surfaced in review: 1. `image: *anchor` was dropped. The recursion path landed in the ScalarNode case which is intentionally a no-op, so a scalar reached through an alias was never recorded. The image-key branch now resolves AliasNode targets before checking for a scalar. 2. `busybox:1.36` and `docker.io/library/busybox:1.36` produced different ImageRef.Repository values and therefore different PURLs, so the same Docker Hub image showed up twice in the BOM. ParseImageRef now canonicalizes single-segment Docker Hub refs to `library/<name>` per the Docker Hub default-namespace rule, matching the OCI purl-spec example `repository_url=docker.io/library/<name>` for official images. Both fixes are covered by new test cases (direct scalar alias + library/ canonicalization across single-segment and fully-qualified inputs).
lalitadithya
previously approved these changes
May 5, 2026
The decode-failure branch in ExtractImagesFromYAML was previously unexercised by tests. Add a regression test that feeds malformed YAML (unclosed flow sequence) and asserts a non-nil error is returned.
lalitadithya
approved these changes
May 5, 2026
This was referenced May 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
make bomplus a reusablepkg/bompackage that produces an authoritative inventory of every container image AICR can deploy — CycloneDX 1.6 JSON + Markdown summary.Motivation / Context
Today, ~19 of 22 components (~86%) defer sub-image selection to upstream Helm chart defaults — there's no way to enumerate "every image AICR can deploy" without rendering charts manually. This blocks security review, air-gap mirror lists, supply-chain attestation, and the docs we owe customers.
Fixes: #740
Related: #739, #741, #744, #745
Type of Change
Component(s) Affected
pkg/bom— new package)tools/bom,Makefile)Implementation Notes
Architecture (refactored from initial proposal — see #739 thread):
pkg/bomholds the reusable core: image extraction from rendered YAML,ImageRefparsing, OCIpurlconstruction, CycloneDX assembly, Markdown rendering. Public API is intentionally minimal sopkg/bundlercan plug in next.tools/bomis a thin CLI wrapper that adds registry parsing,helm templateinvocation, and manifest walking.CycloneDX modeling:
metadata.component= AICR itself (type: application).applicationcomponent withbom-ref: aicr/<name>and helm chart metadata inproperties[].containercomponent withbom-ref: img:<image>andpurl: pkg:oci/<name>@<version>?repository_url=<registry>/<namespace>.dependencies[]wires AICR → components → images.This shape is consumable by Trivy, Grype, and
cosign attest --predicatewithout conversion.Helm template handling: the
recipes/components/*/manifests/tree contains files mixing YAML with Helm templates (network-operator, kubeflow-trainer, gpu-operator, kgateway, nodewright-customizations).pkg/bom.ExtractImagesFromYAMLstrips{{ ... }}directives — control-flow-only lines are dropped, inline directives become a placeholder — so staticimage:values still surface from these files. Covered by tests.Why a new dep (
github.com/CycloneDX/cyclonedx-go): producing valid CycloneDX 1.6 by hand is finicky and the official library is small (~3 transitive deps, all already MIT/Apache compatible). Vendored.Out of scope (follow-up): integrating BOM generation into
aicr bundleso each generated bundle ships its own CycloneDX SBOM. Thepkg/bomAPI was designed for this —BuildBOMtakes a genericMetadata{}so the bundler can identify a recipe/bundle as the root component instead of AICR-the-repo. The follow-up will make per-bundle SBOM the default — no flag required. Will file as a separate issue once this lands.Testing
Live registry smoke test:
make bom(helm rendering enabled): 22 components, 71 unique images across 11 registries.make bom BOM_SKIP_HELM=1(manifest-only): 22 components, 13 image refs.pkg/bomcoverage: 87.7% (new package).Risk Assessment
pkg/bomandtools/bom. Adds one new vendor dependency. TouchesMakefile(one new target, one untouched target). No existing code paths modified. Easy to revert.Rollout notes: none. The
make bomtarget is opt-in. No CI changes in this PR (will follow up to wire BOM into release artifacts and PR diff comments — separate issues).Checklist
make testwith-race)make lint)tools/bom/README.md)git commit -S)