GitOps schema generator and IDE integration - #49717
Conversation
tools/gitops-auto-complete reflects Fleet's GitOps structs into a JSON schema so editors (yaml-language-server) get completion and validation for GitOps YAML. It runs as its own Go module (replace directive back to the repo) so invopop/jsonschema stays out of the root go.mod. Post-processing matches real GitOps files: rename aliases, path/paths file references on section and list-item types, null-tolerant empty keys, type-on-hover descriptions, and agent_options.config.options typed from the generated osqueryOptions struct. Also includes the hand-written scratch schema and test yaml files.
Keep the JSON type on booleans (and objects/arrays) so wrong types are caught, while strings and integers stay untyped to avoid false positives on unquoted YAML values and Fleet ints that marshal as string enums. Restore anyOf errorMessage on the hand-written schema for friendlier required-field messages (a yaml-language-server / VS Code extension).
- Pull Fleet Go doc comments into field descriptions (shown on hover) via
invopop AddGoComments, with the field type appended.
- Mark legacy renameto spellings deprecated so editors steer to the current
names; keep both spellings valid.
- Type controls.{macos,windows,android}_settings with their real Fleet
structs so they offer completion instead of being opaque.
- Merge spec.GitOpsMDM's gitops-only keys (end_user_license_agreement) into
the MDM def, and alias/deprecate any-typed renamed fields.
All standard my-gitops files now validate; remaining flags are genuine
(outdated keys, placeholders, and Fleet's own $-escaping which pure YAML
can't parse).
Each software item must set at least one source: packages need url, hash_sha256, or path; app_store_apps need app_store_id or path; fleet_maintained_apps need slug or path. Expressed as an anyOf of required branches with a shared errorMessage (Fleet enforces this in code, not tags). Also type the identifier keys (url, hash_sha256, app_store_id, slug) as strict strings so a mistyped value like url: 12345 is caught, applied after the null-relaxation pass so it survives.
Restructure the README to a brief description, a How to use section (build the schema, set up with yaml-language-server, Neovim + lazy.nvim example), and a high-level How it works; link yaml-language-server. Remove the temp-test-yaml-schema/ scratch files (the early hand-written schema and sample yamls) — the generated schema is the deliverable.
Move GitOpsSpec, Controls, and the Go-type -> JSON-schema mapping (typeMapper, schemaForType, goTypeToJSON) into types.go; main.go keeps the orchestration and schema post-processing. Rename GitOpsSpec.Settings to TeamSettings to match spec.GitOps (json tag unchanged, schema output identical).
Adds a map of the gitops keys whose omitted/null/empty apply behavior deviates from the default (an omitted key is reset), plus a schema walker that appends a hover note to each so yaml-language-server surfaces it. The behavior was verified against a live server.
Validates comprehensive valid and invalid gitops fixtures against the generated schema (santhosh-tekuri/jsonschema, draft 2020-12), asserts the post-processing invariants survive (declarative notes, rename aliases, required-source rules, typed source keys, config.options, path refs), and checks the committed schema is up to date. Adds a YAMLLS_TEST=1-gated check that runs the fixtures through a real yaml-language-server. The validator and ghodss/yaml are added to the tool module only; the root module is untouched.
Restructure main.go (main first, iterative tree walk, flatter passes with blank-line separation), move the schema-building functions out of the data file (declarative.go -> extra_data.go, now data only), clean up types.go, and rename generated.schema.json -> generated-schema.json. Resolve Fleet source paths from this file's own location via runtime.Caller so the generator produces the same schema regardless of the working directory, instead of assuming it runs from the module root.
relaxNulls now keeps string leaves typed as [string, null] instead of dropping the type, so an unquoted numeric value like version: 13.0 (which fleetctl also rejects) is flagged in the editor while empty placeholders stay valid. Integer and number leaves still drop their type to avoid rejecting int-backed string enums like label_membership_type. Adds regression fixtures for both cases. Also renames the software source passes to installer-reference terminology, follows the Controls -> ControlsWithTypes struct rename through the schema, passes spec.GitOpsMDM into mergeMissingMDMKeys, adds a -h/--help flag, and clarifies the collectNodes and resolveReference internals.
The schema wrongly accepted `- path:` under app_store_apps and
fleet_maintained_apps, which fleetctl rejects (only packages support an
item-level path). Remove path from those defs so it's an unknown key, and
require app_store_id / slug.
Generalize the required-key mechanism to cover labels, policies, and reports:
each requires a name, reports require name and query, with path/paths as
file-reference alternatives.
Model `paths` as a single glob string instead of an array, and add it only to
the defs that support it (policies, labels) rather than the whole-section
defs. Reshape org_settings.yara_rules items from {name, contents} to {path}
to match what gitops parses.
Regenerate the schema and add regression fixtures for each case.
name_template is a valid controls key (spec.GitOpsControls, which gitops validates must be a string), but the tool's hand-written ControlsWithTypes had drifted and lacked it, so the closed schema rejected it. Add it as a string field and regenerate against the merged main (which also adds the same key to the MDM/TeamMDM defs).
From an AI review of the branch: Numeric osquery options lost their type. relaxNulls strips the type off every bare-string scalar leaf, which included the typed config.options.* integers, so a value like distributed_interval: "abc" validated clean. osqueryOptionsSchema now emits [type, null] unions; relaxNulls leaves union-typed nodes alone, so the options stay typed while an empty value still validates. The reflected string-enum ints (label_type, label_membership_type) stay bare-string and are still stripped, which is why relaxNulls can't just union all integers. The yamlls faithfulness test could finalize on a premature empty diagnostic set and let an invalid fixture pass. diagnose now takes the caller's wantErrors expectation and, when errors are expected, waits for a set that actually has errors instead of finalizing on the first (often empty) publish.
custom_host_vitals is a valid top-level gitops key (spec.GitOps / topKeys) that the hand-written GitOpsSpec was missing, so the closed root schema rejected a config fleetctl applies cleanly. Add it, using the gitops input type spec.GitOpsCustomHostVital. Add TestControlsKeysCoverSpec, which fails when spec.GitOpsControls gains a controls key that ControlsWithTypes hasn't mirrored, the drift that hid name_template. Root-key drift still needs manual vigilance, since topKeys is unexported. Rewrite the README to follow Fleet's writing style, add a known-limitations section, and bring the code comments in line with the writing prefs.
command_line_flags reflected to a bare object, so individual flags got no completion or unknown-key validation. Type it from the generated osquery CLI flag struct, closed like config.options, matching fleetctl's strict validation. The same parse also fixes config.options, which was closed but skipped the embedded per-OS structs and so falsely rejected the ~74 valid options they add. One helper now builds both schemas from their generated structs, pulling the embedded keys up into one flat set.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #49717 +/- ##
==========================================
+ Coverage 67.81% 67.86% +0.04%
==========================================
Files 3890 3891 +1
Lines 247631 248347 +716
Branches 13018 13018
==========================================
+ Hits 167942 168551 +609
- Misses 64525 64581 +56
- Partials 15164 15215 +51
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
WalkthroughAdds a Go CLI that generates Fleet GitOps JSON Schema from typed models, Go comments, AST-parsed osquery settings, and GitOps-specific rules. It commits the generated schema, adds valid and invalid YAML fixtures, validates schema invariants and regeneration consistency, and provides an optional YAML language-server integration test using LSP diagnostics. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tools/gitops-auto-complete/yamlls_test.go (1)
126-146: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
request()silently discards notifications while awaiting a response.Any message read from
c.msgsthat isn't the matching response is dropped rather than requeued. This is currently safe only becauserequest()is called once (forinitialize) before anydidOpen/diagnosecall can producepublishDiagnosticsnotifications. If this client is ever extended to callrequest()again mid-test (e.g. ahoverorcompletionrequest while a document is open), any in-flight diagnostics notification would be silently lost, causing the correspondingdiagnose()call to hang until its hard timeout.Routing responses and notifications into separate channels in
readLoop(based on thehasMethod/hasIDcheck already performed there) removes the ambiguity entirely:if _, hasMethod := m["method"]; hasMethod { if _, hasID := m["id"]; hasID { c.respond(m) } else { c.notifs <- m } continue } c.resps <- m
request()would then read only fromc.resps, anddiagnose()only fromc.notifs, eliminating the shared-channel discard hazard.🤖 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/gitops-auto-complete/yamlls_test.go` around lines 126 - 146, Separate JSON-RPC responses from notifications in the yamllsClient read loop, routing messages with a method and ID to the response path and method-only messages to the notification path. Update request() to consume only responses and diagnose() to consume only notifications, preserving matching by request ID while preventing unrelated in-flight notifications from being discarded.
🤖 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.
Nitpick comments:
In `@tools/gitops-auto-complete/yamlls_test.go`:
- Around line 126-146: Separate JSON-RPC responses from notifications in the
yamllsClient read loop, routing messages with a method and ID to the response
path and method-only messages to the notification path. Update request() to
consume only responses and diagnose() to consume only notifications, preserving
matching by request ID while preventing unrelated in-flight notifications from
being discarded.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 33f2ddad-6c0b-45e5-a423-713e65d52b7c
⛔ Files ignored due to path filters (2)
tools/gitops-auto-complete/README.mdis excluded by!**/*.mdtools/gitops-auto-complete/go.sumis excluded by!**/*.sum
📒 Files selected for processing (23)
tools/gitops-auto-complete/extra_data.gotools/gitops-auto-complete/generated-schema.jsontools/gitops-auto-complete/go.modtools/gitops-auto-complete/main.gotools/gitops-auto-complete/schema_test.gotools/gitops-auto-complete/testdata/invalid/app_store_id_wrong_type.ymltools/gitops-auto-complete/testdata/invalid/appstore_path.ymltools/gitops-auto-complete/testdata/invalid/fma_path.ymltools/gitops-auto-complete/testdata/invalid/label_no_name.ymltools/gitops-auto-complete/testdata/invalid/package_no_source.ymltools/gitops-auto-complete/testdata/invalid/paths_wrong_type.ymltools/gitops-auto-complete/testdata/invalid/policy_no_name.ymltools/gitops-auto-complete/testdata/invalid/report_no_name.ymltools/gitops-auto-complete/testdata/invalid/report_no_query.ymltools/gitops-auto-complete/testdata/invalid/unknown_key.ymltools/gitops-auto-complete/testdata/invalid/url_wrong_type.ymltools/gitops-auto-complete/testdata/invalid/version_wrong_type.ymltools/gitops-auto-complete/testdata/invalid/yara_rules_wrong_shape.ymltools/gitops-auto-complete/testdata/valid/global.ymltools/gitops-auto-complete/testdata/valid/references.ymltools/gitops-auto-complete/testdata/valid/team.ymltools/gitops-auto-complete/types.gotools/gitops-auto-complete/yamlls_test.go
|
Related to #32322 |
Summary:
Generates a json schema for valid GitOps yaml files, to be used with yaml-language-server for IDE integration.
This PR includes the actual generated file, so it can be used without running the tool. All files are in
/tools/gitops-autto-complete, so nothing else gets affected.What it adds:
server/fleet/agent_options_generated.go).Limitations:
Dependencies:
invopop/jsonschemareflects Fleet's GitOps structs into the schema.santhosh-tekuri/jsonschema/v6validates the test fixtures against that schema.ghodss/yamldecodes the fixture YAML the way fleetctl does.gitops-autocompletion-demo.mp4
Testing
Summary by CodeRabbit
pathandpathsin supported sections.