README
¶
flux-schema
Flux CLI plugin for Kubernetes schema extraction and manifests validation against JSON Schemas and CEL rules.
[!NOTE] This repository is in early development and the plugin system is not yet available in a stable release of Flux. The instructions for installing and using the plugin will be added here when RFC-0013 has been implemented and released in Flux 2.9 or later.
Available Commands
flux-schema validate [paths...]: Validate Kubernetes manifests against JSON Schemas--schema-location: URL or file path for schemas (repeatable, tried in order);defaultpoints at the built-in catalog--skip-missing-schemas: Skip documents for which no schema can be found--skip-kind: Skip documents matchingkindorapiVersion/kind(repeatable)--skip-json-path: Strip a JSON Pointer field before validation, optionally scoped:[apiVersion/kind:]/path(repeatable)--skip-file: Glob pattern matched against files and dirs; defaults to skipping dotfiles and dot-dirs (repeatable)--skip-cel-rules: Skip evaluation ofx-kubernetes-validationsCEL rules--fail-fast: Exit after the first invalid document--concurrent: Number of concurrent workers (default 8)--insecure-skip-tls-verify: Disable TLS certificate verification when fetching schemas over HTTPS-v, --verbose: Print a line for every document, including valid and skipped ones-o, --output: Output format, one oftext|json|yaml(default:text)--config: Path to a YAML file supplying default values for validate flags (env:FLUX_SCHEMA_CONFIG)
flux-schema extract crd [files...]: Extract JSON Schema from Kubernetes CRD YAMLs-d, --output-dir: Directory to write JSON Schema files to (mutually exclusive with--output-archive)-a, --output-archive: Path to write a gzipped tar archive of JSON Schema files to-f, --output-format: Go template for output file paths (default:{{ .Group }}/{{ .Kind }}_{{ .Version }}.json)--strip-description: Dropdescriptionfields from the generated schemas to reduce their size
flux-schema extract k8s [swagger-file]: Extract JSON Schema from a Kubernetes OpenAPI v2 swagger document--version X.Y.Z: Fetch the swagger fromkubernetes/kubernetesfor the given release tag (mutually exclusive with a swagger file)-d, --output-dir,-f, --output-format,--strip-description: same asextract crd
flux-schema extract openshift [swagger-file]: Extract JSON Schema from anopenshift/apiOpenAPI v2 swagger document--ref REF: Fetch the swagger fromopenshift/apiat the given git ref (e.g.release-4.20); mutually exclusive with a swagger file-d, --output-dir,-f, --output-format,--strip-description: same asextract k8s
Kubernetes Manifests Validation
The validate command reads Kubernetes YAML manifests from one or more files or directories
and validates each document against a JSON Schema resolved from its apiVersion and kind.
When no --schema-location is given, validate uses the flux-schema catalog,
which covers the latest Kubernetes and OpenShift APIs, the stable channel of Gateway API,
and the Flux ecosystem CRDs:
flux-schema validate ./manifests
To validate against your own schemas generated by flux-schema extract, pass the
directory as --schema-location. Bare paths and URLs are auto-expanded to the
catalog layout {{.Group}}/{{.Kind}}_{{.Version}}.json:
flux-schema validate ./manifests --schema-location ./my-schemas
For a different layout, pass a full Go template ending in .json:
flux-schema validate ./manifests \
--skip-missing-schemas \
--schema-location './schemas/{{.Kind}}-{{.GroupPrefix}}-{{.Version}}.json'
Template variables are .Group, .GroupPrefix, .Kind, and .Version.
The --schema-location flag is repeatable and locations are tried in order (the first match wins).
Pass the literal value default to include the flux-schema catalog alongside your own schemas:
flux-schema validate ./manifests \
--schema-location default \
--schema-location https://raw.githubusercontent.com/datreeio/CRDs-catalog/main \
--schema-location './schemas/{{.Kind}}-{{.GroupPrefix}}-{{.Version}}.json'
Manifests can also be piped in and certain documents skipped with --skip-kind:
kustomize build . | flux-schema validate \
--skip-kind 'v1/Service' \
--skip-kind 'source.toolkit.fluxcd.io/v1/ExternalArtifact'
Some manifests carry tooling-injected fields that are stripped at apply time by Flux
(e.g. SOPS-encrypted Secrets). Use --skip-json-path to remove those fields from
validation so the rest of the document is still checked.
flux-schema validate ./manifests \
--skip-json-path 'v1/Secret:/sops' \
--skip-json-path 'Deployment:/sops'
Output example with validation errors:
manifests/releases.yaml - HelmRelease/apps/webapp is invalid: cel violation
- /spec: Invalid value: either 'chart' or 'chartRef' must be set
manifests/sources.yaml - Bucket/apps/webapp-data is invalid: schema violation
- /spec: missing property 'bucketName'
- /spec/interval: got number, want string
- /spec/secretRef/name: got object, want string
- /spec: additional properties 'force' not allowed
manifests/sources.yaml - OCIRepository/apps/webapp is invalid: yaml parse error
- line 18: key "app.kubernetes.io/name" already set in map
manifests/sources.yaml - HelmChart/apps/webapp is valid
manifests/sources.yaml - Secret/apps/auth-sops is skipped: kind skipped
Summary: 5 resources found in 2 files - Valid: 1, Invalid: 3, Skipped: 1
A non-zero exit code is returned when any document is invalid or errored.
Structured output
For CI pipelines and tooling, pass -o json (or -o yaml) to emit a
machine-readable report instead of text:
flux-schema validate ./manifests -o json
See the validation report reference for the full
envelope shape, the reason enum, and an example covering every result
type. The report is versioned by a published
JSON Schema.
Validation rules
- YAML documents with duplicate keys are rejected matching Flux behavior.
- Documents missing both
metadata.nameandmetadata.generateNameare flagged as invalid matching Kubernetes API behavior. metadata.name,generateName,namespace, andlabels/annotationskeys and values are checked against the Kubernetes API server's ObjectMeta rules (DNS-1123, qualified names).- Schemas produced by
flux-schema extract crdclose objects withadditionalProperties: false, so undocumented fields underspecfail validation. - String formats
duration,date,datetime/date-time, andtimeare validated matching Kubernetes API conventions.
CEL validation rules
By default, the validate command extracts the x-kubernetes-validations
rules from the schemas and evaluates them as CEL expressions
using the same engine as the Kubernetes API.
CEL evaluation runs only after JSON Schema validation passes,
and any rule violations are reported with the cel-violation reason.
Transition rules referencing oldSelf evaluate with no prior state,
matching the Kubernetes API behavior on CREATE.
The CEL validation can be disabled with the --skip-cel-rules flag.
Config file
Flag values can be pre-set in a YAML config file and referenced with --config
or with the FLUX_SCHEMA_CONFIG environment variable.
This keeps long invocations out of CI scripts and makes validation reproducible
across developers:
flux-schema validate ./manifests --config .flux-schema.yaml
The file has a version (required, must be "1") and a validate section
whose keys mirror the CLI flag names:
version: "1"
validate:
schema-location:
- default
- https://raw.githubusercontent.com/datreeio/CRDs-catalog/main
skip-kind:
- v1/ServiceAccont
- source.toolkit.fluxcd.io/v1/ExternalArtifact
skip-json-path:
- Secret:/sops
skip-file:
- '.*'
- kustomization.yaml
skip-cel-rules: false
skip-missing-schemas: false
verbose: true
fail-fast: false
concurrent: 8
insecure-skip-tls-verify: false
output: text
Rules:
- CLI flags override config values. Setting
--verbose=falsewins oververbose: truein the file. - Setting
--configoverridesFLUX_SCHEMA_CONFIG. When both are set, the flag wins and the env var is ignored. - Manifest paths stay positional. The config file configures how to validate; paths are given on the command line.
GitHub Action
For GitOps repositories, the fluxcd/flux-schema/actions/validate
action runs validation across a directory tree on every pull request.
It auto-detects kustomize overlays, builds them with kubectl kustomize, and
validates the rendered manifests against the catalog (including CEL rules):
name: validate
on:
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: azure/setup-kubectl@v4
- uses: fluxcd/flux-schema/actions/setup@main
- uses: fluxcd/flux-schema/actions/validate@main
with:
path: "."
schema-location: |
default
https://raw.githubusercontent.com/datreeio/CRDs-catalog/main
See the action README for the full input reference.
Kubernetes CRD Extraction
The extract crd command reads Kubernetes CustomResourceDefinition YAML
and writes one JSON Schema file per CRD version.
The input can be a bare CRD, a List of CRDs, or a multi-document YAML stream.
Generate schemas for every CRD installed in a cluster, using the per-group-directory layout:
kubectl get crds -o yaml | flux-schema extract crd -d ./schemas
You can supply -f, --output-format with a Go template to change the layout, e.g. the
kubeconform/kubeval flat layout:
flux-schema extract crd crds.yaml -f '{{ .Kind }}-{{ .GroupPrefix }}-{{ .Version }}.json'
The output is compatible with
kubeconformandkubeval, making this command a drop-in replacement for kubeconform's openapi2jsonschema.py script.
To bundle the schemas into a gzipped tar archive instead of writing to a directory,
use -a, --output-archive:
kustomize build config/crd | flux-schema extract crd -a dist/crd-schemas.tar.gz
The archive path must end in .tar.gz or .tgz, and its parent directory is created if missing.
-f, --output-format still controls the entry paths inside the archive, so a nested template produces
subdirectories within the tarball.
Supported template variables (all lowercased at render time):
| Variable | Example |
|---|---|
.Group |
source.toolkit.fluxcd.io |
.GroupPrefix |
source |
.Kind |
gitrepository |
.Version |
v1 |
An empty .Group (Kubernetes core API, e.g. apiVersion: v1) is normalized to core, and .GroupPrefix
is derived from .Group when unset. So a core Pod renders as core/pod_v1.json with the default template
and resolves to the same path when validate looks up its schema.
Note that the generated schemas apply the following OpenAPI → JSON Schema transformations:
- Objects with
propertiesare closed withadditionalProperties: false, except under nodes marked withx-kubernetes-preserve-unknown-fields: true, which stay open so free-form maps validate correctly. - Integer-or-string fields are rewritten to
oneOf: [{type: string}, {type: integer}]. Both the legacyformat: int-or-stringand the structuralx-kubernetes-int-or-string: trueforms are recognized.
Kubernetes OpenAPI Extraction
The extract k8s command reads a Kubernetes OpenAPI v2 swagger document and writes
one JSON Schema file per kind listed under x-kubernetes-group-version-kind. Helper
types (e.g. PodSpec, ObjectMeta) are not emitted as standalone files — they are
inlined into the kinds that reference them.
Fetch the upstream swagger for a Kubernetes release and generate schemas:
flux-schema extract k8s --version 1.35.0 -d ./schemas
You can also supply the swagger file directly, e.g. from a live cluster:
kubectl get --raw /openapi/v2 | flux-schema extract k8s -d ./schemas
The same -f, --output-format template used by extract crd works here, so the
generated files remain resolvable by validate with a single --schema-location
for both built-ins and CRDs. Core API kinds (apiVersion: v1) render under the
core/ group directory.
The emitted schemas are the standalone-strict variant:
- Every
$refis inlined so schemas have no cross-file dependencies. - Objects with
propertiesare closed withadditionalProperties: false, except under nodes markedx-kubernetes-preserve-unknown-fields: true. - Integer-or-string fields are rewritten to
oneOf: [{type: string}, {type: integer}]. - Optional fields are marked nullable (
type: [<t>, "null"]), matching the Kubernetes API server's behavior of acceptingnullfor unset optional values. apiVersionandkindare injected into every kind's properties and required list.
OpenShift OpenAPI Extraction
The extract openshift command reads the openshift/api OpenAPI v2 swagger document
and writes one JSON Schema file per OpenShift resource. Only definitions in the
openshift namespace are emitted; the upstream Kubernetes types
embedded in the same document (e.g. Pod) are inlined.
Fetch the swagger from openshift/api at a release branch:
flux-schema extract openshift --ref release-4.20 -d ./schemas
You can also supply the swagger file directly, e.g. from a URL:
curl -sL https://raw.githubusercontent.com/openshift/api/release-4.20/openapi/openapi.json \
flux-schema extract openshift -d ./schemas
The same -f, --output-format template used by extract k8s works here, so the
generated files remain resolvable by validate with a single --schema-location
for both Kubernetes and OpenShift resources.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
flux-schema
command
|
|
|
internal
|
|
|
extractor
Package extractor converts Kubernetes API definitions into standalone-strict JSON Schema documents suitable for offline validators and editor tooling.
|
Package extractor converts Kubernetes API definitions into standalone-strict JSON Schema documents suitable for offline validators and editor tooling. |
|
tmpl
Package tmpl renders Go text/template strings with the CRD schema variables shared across flux-schema commands.
|
Package tmpl renders Go text/template strings with the CRD schema variables shared across flux-schema commands. |
|
validator
Package validator validates Kubernetes YAML manifests against JSON Schemas resolved from one or more schema location templates.
|
Package validator validates Kubernetes YAML manifests against JSON Schemas resolved from one or more schema location templates. |
|
yamldoc
Package yamldoc provides a bufio.SplitFunc that splits a byte stream into individual YAML documents on "\n---" separators.
|
Package yamldoc provides a bufio.SplitFunc that splits a byte stream into individual YAML documents on "\n---" separators. |