Skip to content

feat: add query command for hydrated recipe value extraction#445

Merged
mchmarny merged 7 commits intomainfrom
feat/query-command
Mar 20, 2026
Merged

feat: add query command for hydrated recipe value extraction#445
mchmarny merged 7 commits intomainfrom
feat/query-command

Conversation

@mchmarny
Copy link
Copy Markdown
Member

Summary

Implements ADR-004 — a new aicr query command and /v1/query API endpoint that resolves a recipe from criteria and extracts a specific fully hydrated value using a dot-path selector.

This addresses the gap where AICR embeds curated configuration metadata into the binary but users have no read-only way to inspect it without running a full aicr bundle to disk.

What's new

  • CLI command (pkg/cli/query.go): same criteria flags as recipe, plus required --selector with dot-path syntax (consistent with Helm --set and yq)
  • Core logic (pkg/recipe/query.go): HydrateResult() merges all base/overlay/inline values per component; Select() walks dot-path with available-keys hints on typos
  • API endpoint (pkg/recipe/handler_query.go): GET/POST /v1/query with same criteria + selector parameter
  • yq-style leading dot: .components.gpu-operator.chart and components.gpu-operator.chart both work
  • Shell-friendly output: scalars print as plain text (no YAML wrapper), complex values as YAML/JSON

Additional fixes

  • Fix missing sidebar entry for gke-tcpxo-networking doc
  • Fix pre-existing goconst lint issues in metadata_test.go
  • Exclude node_modules from grype scan (esbuild binary compiled with go1.23.12 was triggering false positive stdlib CVEs)

Test plan

  • make qualify passes (test, lint, e2e, scan)
  • 24 unit tests across query.go (12), handler_query.go (12)
  • Coverage: Select 100%, HydrateResult 97.5%, HandleQuery 80%, parseQueryRequestFromBody 85.7%
  • Manual CLI testing with various selectors (scalar, subtree, dot-prefix, empty)
  • Manual API testing via make server + curl

@mchmarny mchmarny requested a review from a team as a code owner March 19, 2026 23:51
@mchmarny mchmarny self-assigned this Mar 19, 2026
@mchmarny mchmarny added this to the M2 - KubeCon EU milestone Mar 19, 2026
Implements ADR-004. New `aicr query` CLI command and `/v1/query` API
endpoint that resolves a recipe from criteria, hydrates all component
values (base + overlay + inline overrides merged), and extracts a
specific value using a dot-path selector.

- CLI: same criteria flags as recipe, plus required --selector
- API: GET/POST /v1/query with criteria + selector parameter
- Selector: dot-delimited paths, yq-style leading dot supported
- Output: scalars as plain text, complex values as YAML/JSON
- Error UX: typo hints with available keys at divergence point
- Add missing sidebar entry for gke-tcpxo-networking doc
- Exclude node_modules from grype scan (esbuild binary compiled
  with go1.23.12 was triggering false positive stdlib CVEs)
@mchmarny mchmarny force-pushed the feat/query-command branch from c50f4e1 to bd1a08b Compare March 19, 2026 23:55
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 19, 2026

Coverage Report ✅

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

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/NVIDIA/aicr/pkg/api 0.00% (ø)
github.com/NVIDIA/aicr/pkg/cli 29.54% (+2.27%) 👍
github.com/NVIDIA/aicr/pkg/recipe 90.30% (+0.04%) 👍

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/NVIDIA/aicr/pkg/api/server.go 0.00% (ø) 19 0 19
github.com/NVIDIA/aicr/pkg/cli/query.go 42.86% (+42.86%) 77 (+77) 33 (+33) 44 (+44) 🌟
github.com/NVIDIA/aicr/pkg/cli/recipe.go 76.54% (+15.48%) 81 (-32) 62 (-7) 19 (-25) 🎉
github.com/NVIDIA/aicr/pkg/cli/root.go 44.19% (ø) 43 19 24
github.com/NVIDIA/aicr/pkg/recipe/criteria.go 90.34% (+0.19%) 290 (+26) 262 (+24) 28 (+2) 👍
github.com/NVIDIA/aicr/pkg/recipe/handler_query.go 82.54% (+82.54%) 63 (+63) 52 (+52) 11 (+11) 🌟
github.com/NVIDIA/aicr/pkg/recipe/query.go 98.33% (+98.33%) 60 (+60) 59 (+59) 1 (+1) 🌟

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.

@mchmarny mchmarny requested review from dims, lalitadithya and yuanchen8911 and removed request for dims March 19, 2026 23:57
Copy link
Copy Markdown
Contributor

@yuanchen8911 yuanchen8911 left a comment

Choose a reason for hiding this comment

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

Review: feat: add query command for hydrated recipe value extraction

Core logic (HydrateResult, Select) is clean and well-tested. ADR-004 compliance is faithful overall. A few issues to address before merge:

1. POST /v1/query bypasses criteria validation

The GET path parses criteria through ParseCriteriaFromRequest, but the POST path unmarshals the request body directly into QueryRequest.Criteria via json.Unmarshal/yaml.Unmarshal. Any validation performed by the parser is skipped. The downstream AllowLists check catches some cases, but invalid field values that the parser would reject can slip through.

pkg/recipe/handler_query.go:83-95

2. --output flag is exposed but silently ignored

queryCmdFlags() inherits all recipeCmdFlags() including --output. However, the query action never writes to a file — it always prints to stdout. A user passing --output result.yaml gets no error and no file written.

Either filter --output from the inherited flags, or wire it up to write to the specified file.

pkg/cli/query.go:29-36

3. --format json produces invalid output for scalar values

writeQueryResult prints string/bool/number scalars via fmt.Println(v) regardless of the output format. With --format json, a string scalar like 570.86.16 is printed as bare text instead of "570.86.16" — not valid JSON. Lists and maps are handled correctly.

pkg/cli/query.go:182-192

4. /v1/query endpoint missing from OpenAPI spec

The route is registered in pkg/api/server.go and the ADR calls out API parity, but api/aicr/v1/server.yaml is not updated. This means generated clients and API documentation won't include the new endpoint.

5. Code duplication: buildRecipeFromCmd

buildRecipeFromCmd in pkg/cli/query.go:120-178 is a near-verbatim copy of the recipe command action in pkg/cli/recipe.go:152-224. Consider extracting a shared helper so recipe resolution logic only lives in one place.

Minor

  • .grype.yaml node_modules exclusion and metadata_test.go constant extractions are unrelated to the feature — consider splitting into a separate PR.
  • pkg/cli/query.go has 0% test coverage (70 statements). The pkg/recipe tests cover the core logic well, but CLI output formatting (writeQueryResult, writeComplexValue) is untested.

@mchmarny
Copy link
Copy Markdown
Member Author

mchmarny commented Mar 20, 2026

Addressed all items:

  1. POST criteria validation — Added Criteria.Validate() that runs fields through the same parsers as ParseCriteriaFromValues. Called in POST path before proceeding.
  2. --output flag — Filtered out from queryCmdFlags() since query always prints to stdout.
  3. --format json scalars — All values now go through json.MarshalIndent when format is JSON, producing valid output like "570.86.16".
  4. OpenAPI spec — Added full GET/POST /v1/query definitions with QueryRequest schema, parameters, and response codes.
  5. Code duplication — Refactored recipe.go action to use shared buildRecipeFromCmd (~90 lines removed).
  6. Unrelated changes — Already in separate commits; splitting would require history rewrite and committing as it is on main generates error during PR qualification
  7. CLI test coverage — Added query_test.go covering writeQueryResult (scalar/complex × YAML/JSON) and flag filtering.

- Add Criteria.Validate() for POST body validation parity with GET path
- Remove --output flag from query command (always prints to stdout)
- Fix --format json for scalar values to produce valid JSON
- Add /v1/query endpoint to OpenAPI spec with QueryRequest schema
- Deduplicate recipe resolution by reusing buildRecipeFromCmd in recipe.go
- Add CLI tests for writeQueryResult and flag filtering
Copy link
Copy Markdown
Contributor

@yuanchen8911 yuanchen8911 left a comment

Choose a reason for hiding this comment

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

All five items from the previous review are addressed:

  1. POST criteria validationCriteria.Validate() added with parser-equivalent checks, called in POST path before proceeding. Test covers invalid criteria returning 400.
  2. --output flag — Filtered out of queryCmdFlags(). Test verifies exclusion.
  3. --format json scalars — All JSON output now goes through json.MarshalIndent, producing valid JSON for all types. Test verifies quoted strings.
  4. OpenAPI spec — Full GET/POST /v1/query definitions with QueryRequest schema, examples, and response codes.
  5. Code duplicationrecipe.go refactored to use shared buildRecipeFromCmd. Constraint warning logging correctly stays in recipe action only.

LGTM.

@yuanchen8911
Copy link
Copy Markdown
Contributor

Non-blocking follow-up: HydrateResult omits several ComponentRef fields (valuesFile, patches, manifestFiles, cleanup, expectedResources, healthCheckAsserts) and the top-level validation block from RecipeResult. This means --selector . and components.<name> return a subset of the full struct, not the "entire hydrated recipe/component" described in the ADR.

If these fields are intentionally excluded as internal implementation details (file references, health check plumbing), consider documenting that in the ADR or query.go godoc so the contract is explicit. If they should be included, a follow-up PR can add them.

@mchmarny
Copy link
Copy Markdown
Member Author

HydrateResult omits several ComponentRef fields (valuesFile, patches, manifestFiles, cleanup, expectedResources, healthCheckAsserts) and the top-level validation block from RecipeResult. This means --selector . and components. return a subset of the full struct, not the "entire hydrated recipe/component" described in the ADR.

The omissions are intentional and appropriate. The excluded fields fall into two categories:

  1. Internal file references (valuesFile, manifestFiles, patches) — These are build-time paths into the embedded recipe data. They're meaningless to consumers; HydrateResult resolves them into the actual values map, which is the whole point of hydration.
  2. Operational plumbing (cleanup, expectedResources, healthCheckAsserts, validation) — These are used by validate and bundle commands internally. They're not part of the "what configuration does this recipe produce" contract that query serves.

@mchmarny mchmarny merged commit 46736f8 into main Mar 20, 2026
24 checks passed
@mchmarny mchmarny deleted the feat/query-command branch March 20, 2026 18:10
nvidiajeff added a commit that referenced this pull request Mar 21, 2026
- Add missing /v1/query endpoint documentation (exists in OpenAPI spec since #445)
- Add /v1/query to feature comparison table and routes example
- Add missing gke-nccl-tcpxo to bundler table
- Fix content type application/yaml to application/x-yaml in curl example
nvidiajeff added a commit that referenced this pull request Mar 21, 2026
- Add missing /v1/query endpoint documentation (exists in OpenAPI spec since #445)
- Add /v1/query to feature comparison table and routes example
- Add missing gke-nccl-tcpxo to bundler table
- Fix content type application/yaml to application/x-yaml in curl example
xdu31 pushed a commit to xdu31/aicr that referenced this pull request Mar 24, 2026
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.

2 participants