### Summary: Generates a json schema for valid GitOps yaml files, to be used with [yaml-language-server](https://github.com/redhat-developer/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: - Complete json schema that defines valid GitOps yaml files and can be integrated with [yaml-language-server](https://github.com/redhat-developer/yaml-language-server). - Auto-completion, error checking, type checking, descriptions. - Defines all keys for osquery options/flags (based on `server/fleet/agent_options_generated.go`). - Additional validation: required keys, strings that must be enclosed in quotation marks, path support. - Additional data: descriptions from code comments, notices for fields that don't reset if null or empty. #### Limitations: - Some structs and data are duplicated into the tool and will inevitebly mismatch over time, because the structs used for gitops are not sufficient for the schema generation: - Some fields use an interface/any type that so can't be used for the schema generation. - Some important details are not encoded in the type or struct tags for gitops fields at all. - Some details (like required fields) are encoded in the Validate() interface, but the IDE integration cannot run Go code. - Doesn't work with all yaml file types used for gitops (like a yaml file that specifies multiple software packages), only the default/fleet level files. This will require having a subsection of the schema for each type of file, and some way to detect what it actually is (maybe specifying the schema in the file itself). - Requires manual setup to integrate with IDE, it's not an easy to use extension currently. #### Dependencies: - `invopop/jsonschema` reflects Fleet's GitOps structs into the schema. - `santhosh-tekuri/jsonschema/v6` validates the test fixtures against that schema. - `ghodss/yaml` decodes the fixture YAML the way fleetctl does. https://github.com/user-attachments/assets/b6ffacd0-e602-41a2-b46a-7d10300c6ea5 ## Testing - [x] QA'd all new/changed functionality manually - I have been using and working on this for the past week so it's in a pretty good state, but some descriptions or keys are probably still missing. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added GitOps YAML auto-completion powered by a comprehensive JSON Schema. * Added validation for GitOps configuration structure, supported fields, data types, required combinations, and unknown keys. * Added support for external file references using `path` and `paths` in supported sections. * Added clearer guidance for deprecated fields and special field behaviors. * **Bug Fixes** * Improved detection of incorrectly typed values and invalid configuration shapes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
121 lines
4.4 KiB
Go
121 lines
4.4 KiB
Go
package main
|
|
|
|
// Data tables that Fleet's Go structs don't express but GitOps YAML relies on:
|
|
// declarative apply notes, path-reference support, and installer-reference keys.
|
|
|
|
// gitops sends a fully materialized config, so omitting a key normally resets it.
|
|
// These hover notes cover the keys that instead keep their value.
|
|
const (
|
|
declarativeKeepAlways = "GitOps: kept unchanged when omitted, null, or empty."
|
|
declarativeKeepUnlessEmpty = "GitOps: kept unchanged when omitted or null; cleared when set to an empty value."
|
|
declarativeKeepOnOmit = "GitOps: kept unchanged when omitted; cleared when set to null or empty."
|
|
)
|
|
|
|
// declarativeExceptions maps a gitops key (dotted path) to its hover note. Only the
|
|
// exceptions to the reset-on-omit default are listed, verified against a live apply.
|
|
var declarativeExceptions = map[string]string{
|
|
// Google service-account credentials, preserved so a re-apply need not resend
|
|
// the secret. UI GitOps mode is merged onto the existing config.
|
|
"org_settings.integrations.google_calendar.api_key_json": declarativeKeepAlways,
|
|
"org_settings.integrations.google_workspace.api_key_json": declarativeKeepAlways,
|
|
"org_settings.gitops": declarativeKeepAlways,
|
|
|
|
// host_expiry is the documented exception that isn't reset when omitted, but
|
|
// an explicit empty object still resets it. Applies at team and org level.
|
|
"settings.host_expiry_settings": declarativeKeepUnlessEmpty,
|
|
"org_settings.host_expiry_settings": declarativeKeepUnlessEmpty,
|
|
|
|
// Label host membership: omitting keeps the current members, an explicit empty
|
|
// list clears them. From the docs and label parsing.
|
|
"labels.hosts": declarativeKeepOnOmit,
|
|
}
|
|
|
|
// pathReferenceDefinitions are the $defs that accept a `path` (one external file) in
|
|
// place of inline content. pathsReferenceDefinitions additionally accept `paths` (a
|
|
// single glob string). Defs whose Go type embeds fleet.BaseItem (reports, scripts,
|
|
// configuration_profiles) already get both from reflection, so they aren't listed.
|
|
var pathReferenceDefinitions = []string{
|
|
"GitOpsOrgSettings", "GitOpsFleetSettings", "AgentOptions", "ControlsWithTypes",
|
|
"SoftwarePackageSpec",
|
|
}
|
|
|
|
var pathsReferenceDefinitions = []string{
|
|
"GitOpsPolicySpec", "LabelSpec",
|
|
}
|
|
|
|
// requiredKeyRule gates a $def: an item is valid if it has all the keys of any one of
|
|
// its validKeyCombinations. So {{"a"},{"b"}} means "a or b" and {{"a","b"}} means
|
|
// "a and b". Fleet enforces these at gitops apply time in validation code rather than
|
|
// struct tags. Where an item can also be a file reference, path/paths are listed as
|
|
// their own combinations.
|
|
type requiredKeyRule struct {
|
|
definition string
|
|
message string
|
|
validKeyCombinations [][]string
|
|
}
|
|
|
|
var requiredKeys = []requiredKeyRule{
|
|
{
|
|
definition: "SoftwarePackageSpec",
|
|
message: "A package must set one of: url, hash_sha256, or path.",
|
|
validKeyCombinations: [][]string{
|
|
{"url"},
|
|
{"hash_sha256"},
|
|
{"path"},
|
|
},
|
|
},
|
|
{
|
|
definition: "TeamSpecAppStoreApp",
|
|
message: "An app_store_apps entry must set app_store_id.",
|
|
validKeyCombinations: [][]string{{"app_store_id"}},
|
|
},
|
|
{
|
|
definition: "MaintainedAppSpec",
|
|
message: "A fleet_maintained_apps entry must set slug.",
|
|
validKeyCombinations: [][]string{{"slug"}},
|
|
},
|
|
{
|
|
definition: "LabelSpec",
|
|
message: "A label must set name (or reference a file with path/paths).",
|
|
validKeyCombinations: [][]string{
|
|
{"name"},
|
|
{"path"},
|
|
{"paths"},
|
|
},
|
|
},
|
|
{
|
|
definition: "GitOpsPolicySpec",
|
|
message: "A policy must set name (or reference a file with path/paths).",
|
|
validKeyCombinations: [][]string{
|
|
{"name"},
|
|
{"path"},
|
|
{"paths"},
|
|
},
|
|
},
|
|
{
|
|
definition: "Query",
|
|
message: "A report must set name and query (or reference a file with path/paths).",
|
|
validKeyCombinations: [][]string{
|
|
{"name", "query"},
|
|
{"path"},
|
|
{"paths"},
|
|
},
|
|
},
|
|
{
|
|
definition: "YaraRule",
|
|
message: "A yara_rules entry must set path.",
|
|
validKeyCombinations: [][]string{{"path"}},
|
|
},
|
|
}
|
|
|
|
// strictStringKeys are the keys kept as strict strings, so a wrong-typed value like
|
|
// `url: 12345` is caught. `path`/`paths` are excluded so they stay nullable.
|
|
var strictStringKeys = map[string][]string{
|
|
"SoftwarePackageSpec": {"url", "hash_sha256"},
|
|
"TeamSpecAppStoreApp": {"app_store_id"},
|
|
"MaintainedAppSpec": {"slug"},
|
|
"LabelSpec": {"name"},
|
|
"GitOpsPolicySpec": {"name"},
|
|
"Query": {"name", "query"},
|
|
}
|