Manifest validation
Nyl runs configured validators against final Kubernetes manifests. render and
render-tree emit their output even when validation fails so it can be inspected;
they return a failing exit status. render-tree --check writes no output.
Validation must succeed before calculating diffs, applying resources, or publishing
a target.
The CI image includes the pinned kubeconform executable. For local development,
install the tools in the project mise configuration.
Configure validation
Section titled “Configure validation”Add a validator to nyl.toml:
[validation]enabled = false
[validation.kubeconform]strict = truebuiltin_schemas = "cached"schema_locations = []skip = []timeout_seconds = 60The kubeconform table selects that validator. enabled = true runs configured
validators automatically for render, diff, apply, render-tree,
diff-tree, and publish-tree. --validate enables them for one invocation;
--no-validate suppresses automatic validation. Requesting validation without a
configured validator is an error.
strict = true rejects unknown fields as well as duplicate YAML keys. Captured
CRDs are vendored whole, and both strict and permissive schema variants are
derived from them, so this setting can change without recapturing. Both variants validate declared constraints; permissive mode
allows extra fields where the schema does not explicitly constrain them. Strict
mode respects fields that explicitly preserve unknown properties.
nyl render-tree --target production --output-dir rendered --validatenyl diff-tree --target production --validatenyl publish-tree --target production --validatenyl render app.yaml --kube-version 1.31.4 --validateTarget-aware validation uses the Cluster’s effective Kubernetes version.
Targetless validation requires --kube-version; Helm rendering also requires
--kube-api-versions. The schema lookup normalizes a leading v, distribution
suffixes, and a missing patch version, without falling back to another version.
Validation runs on cache hits, render-tree --check, and
publish-tree --dry-run. A filtered diff-tree validates the entire desired
tree, then selects the differences to display. Its baseline is allowed to
contain invalid resources so that corrections remain diffable.
apply validates the submitted set before writing resources or release state.
In append mode, diff validates its composed desired set, while apply
validates the resources submitted in that invocation.
Diagnostics and summaries go to stderr, preserving manifest and diff stdout.
Each destination reports schema preparation, the number of resources submitted
to kubeconform, and its elapsed validation time. A final summary counts resource
outcomes across destinations. Borrowed CRD snapshots
identify their source Cluster. Failures are reported when that destination’s
validation completes.
Invalid resources, missing schemas, tool failures, and timeouts fail the command.
Explicit skip entries use apiVersion/kind, such as example.com/v1/Widget;
the summary reports skipped resources. Lists are expanded for schema discovery
and validation, with item paths retained in diagnostics. A skipped List excludes
its entire contents. Schema validation does not execute CEL,
admission webhooks, or conversion webhooks, and does not establish deployment
ordering or compatibility with resources outside the validated input.
Capture cluster CRD schemas
Section titled “Capture cluster CRD schemas”Cluster capture explicitly reads live capabilities and CRDs. Its default policy is equivalent to:
[capture.cluster]crds = truenyl capture cluster stagingnyl capture cluster staging --crds --context staging-adminnyl capture cluster staging --checknyl capture cluster staging --no-crdsThe command refreshes committed Kubernetes capabilities and, when enabled,
schemas for every served CRD version. --check compares the enabled capture
outputs with the live cluster without writing. --no-crds refreshes capabilities
while preserving schema files; it does not certify those schemas as current.
Capturing schemas is enabled by default, including when nyl init invokes
capture, and requires permission to list CRDs. Set [capture.cluster] crds = false
to default to capabilities-only capture; --crds overrides that setting.
A CRD read failure aborts capture before writing. nyl vendor consumes committed
captures and does not contact clusters.
A capture with CRDs replaces that Cluster’s schema inventory with the served
CRD versions currently in the cluster, removing entries for deleted CRDs and
versions that are no longer served. Unreferenced schema blobs remain on disk
until nyl vendor --prune; blobs referenced by another Cluster are retained.
Snapshots use vendor.path, or vendor/ when that setting is absent:
vendor/ clusters/staging/schemas.json schemas/blobs/<sha256>.jsonThe cluster inventory lists each CRD’s group, kind, scope, and served versions,
and references its vendored definition blob. Equal definitions are stored once
across clusters. Volatile resource metadata, status, and conversion webhook CA
bundles are excluded, so a definition changes only with its API contract.
Inventories written by earlier Nyl versions are not read; recapture them with
nyl capture cluster <name> --crds and remove their old blobs with
nyl vendor --prune. Commit the inventories and blobs with the
Cluster configuration. Capture and vendoring generate schemas/.gitattributes
to treat blobs as binary in Git, suppressing text diffs and merges and preserving
the exact bytes required by their hashes. Blobs remain JSON in ordinary Git;
inventories remain diffable.
With CRDs vendored, the Cluster’s spec.kubernetes records only the APIs and
cluster-scoped kinds the CRDs do not serve and sets vendoredCrds: true.
Rendering and validation add the CRD-served API versions and scopes back from
the snapshot.
A snapshot includes its source capabilities fingerprint. Validation rejects a mismatch and instructs recapture, including when a capture was interrupted between updating the schema inventory and Cluster source.
Borrow another Cluster’s API contract
Section titled “Borrow another Cluster’s API contract”A production Cluster can use a committed staging contract when production is inaccessible to the rendering pipeline:
apiVersion: k8s.nyl/v1kind: Clustermetadata: name: productionspec: destination: server: https://production.example.com apiContractFrom: clusterRef: name: staging mode: allall inherits Kubernetes version, API capabilities, and the CRD schema source.
Omit spec.kubernetes in this mode. schemas borrows only the CRD schema source
and requires production’s own spec.kubernetes block. Rendering and validation
use the same effective capabilities.
Destinations, values, labels, and live connection settings remain local.
References may form acyclic chains. nyl get clusters reports effective versions
and borrowed provenance. Capture the source Cluster; capture of an inheriting
Cluster is rejected with the source name.
Borrowing asserts API compatibility; it is not an observation of production. Validation reports the destination and the source of its captured schemas. Generated Argo CD Applications and AppProjects use their ArgoCDInstance Cluster’s contract, independently of the workload destination.
Validate an operator installation or upgrade
Section titled “Validate an operator installation or upgrade”Complete target-tree validation uses desired CRDs automatically. This applies to
render-tree, diff-tree, and publish-tree whenever validation is enabled:
nyl publish-tree --target production --validateUse --no-use-desired-crds to validate against local or captured CRD schemas.
This switch does not enable validation. File-based render, diff, and apply
require --use-desired-crds to enable validation using desired CRDs; it cannot
be combined with source/output kind filters or --append-release.
Schemas are resolved in this order, independently for each destination:
- Desired CRDs, when enabled for the invocation.
- Configured local schema locations.
- The effective Cluster’s captured CRD schemas.
- The pinned registry for built-in Kubernetes APIs.
A desired CRD supersedes its entire group/kind definition. Removed and unserved versions cannot fall back to captured schemas. Conflicting desired definitions fail. A group/kind absent from desired CRDs retains ordinary schema resolution.
Validation checks compatibility of resources included in the render. It does not check resources in other targets or existing cluster resources omitted from the input, establish upgrade ordering, or perform stored-version migration. Tree diff selection filters do not narrow the complete desired validation input.
For APIs managed elsewhere, commit schemas locally and configure a lookup:
[validation.kubeconform]schema_locations = [ 'schemas/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json',]Lookup templates support kubeconform’s Group, ResourceKind (lowercase),
ResourceAPIVersion, KindSuffix, NormalizedKubernetesVersion, and
StrictSuffix fields. Paths and relative schema references must stay inside the
project and must not traverse symlinks. A location without a JSON extension uses
the standard Kubernetes schema directory layout. External HTTP references in
local schemas are not admitted; commit their dependencies locally.
Reproducible and offline CI
Section titled “Reproducible and offline CI”Built-in schemas come from an immutable commit of
yannh/kubernetes-json-schema.
The effective Kubernetes version selects the version directory within that
registry snapshot. The kubeconform executable version and registry revision are
independent pins. A project can set builtin_schema_revision to a full commit
SHA when it needs a different registry snapshot.
CustomResourceDefinition schemas use the registry’s shared definitions because their recursive schemas are not available as standalone files. Their complete reference graph uses the selected Kubernetes version and strictness policy.
By default, built-ins are downloaded into a disposable cache. To require committed schemas during validation:
[vendor]mode = "required"
[validation.kubeconform]builtin_schemas = "vendor-used"nyl vendornyl vendor --checknyl diff-tree --target production --validatenyl publish-tree --target production --validatebuiltin_schemas selects one of three modes:
| Mode | Vendoring | Validation |
|---|---|---|
cached (default) | No builtin schema population | Download into a disposable cache as needed |
vendor-used | Schemas required by selected target renders | Require vendored schemas |
vendor-all | Every JSON schema in each selected Kubernetes version directory | Require vendored schemas |
Both vendor modes include catalog destinations and schema dependencies.
vendor-all uses the configured strictness and immutable registry revision,
regardless of local schema overrides or resource exclusions. It records complete
directory inventories so nyl vendor --check can verify coverage offline.
Adding a builtin resource kind for those versions requires no new vendor run.
Changing Kubernetes version, strictness, or registry revision requires vendoring
its matching schemas. In both vendor modes validation
fails on missing vendored schemas and never downloads a fallback. A targetless
render must also have all of its required built-ins available locally.
nyl vendor repairs missing or corrupt builtin schema blobs from the cache or
pinned registry. nyl vendor --refresh fetches required builtins and their
dependencies again; validation and --check reject damaged blobs without repair.
nyl vendor does not access clusters or refresh captures. --prune preserves
blobs referenced by every captured Cluster, including inherited sources, and
aborts schema pruning if an inventory is malformed.
Publication loads validation policy, inheritance references, and schemas from the source snapshot selected for the published artifact. Commit these inputs before publishing; invocation flags apply to that selected snapshot.
Findings and report exports
Section titled “Findings and report exports”Human findings group field errors beneath each resource and include its rendered location and recorded Nyl authoring provenance. The expansion trace identifies source documents, Components, HelmCharts, remote repository revisions, and Nyl operations when available. It is a resource-level trace, not a field-to-template source map. Schema instance paths come from kubeconform; source line numbers are not inferred from them.
Each failure leads with the validated resource and its field errors. Rendered:
locates the output document, Source: locates the authoring document, and
successive Expanded from: rows identify the resources in its expansion trace.
Document suffixes and provenance API versions share a dimmed, aligned column
within each resource’s details.
Terminal and CI findings use a bold cyan header and red failure labels, following
Nyl’s --color policy and NO_COLOR. Text and JSON exports remain unstyled.
Export the same results independently of manifest output:
nyl render-tree --output-dir tmp \ --validation-output json:validation.json \ --validation-output text:validation.txt
nyl render-tree --check --output-dir tmp \ --validation-output json:- --no-validation-stderr--validation-output FORMAT:PATH is repeatable and supports json and text.
An export request enables configured validation and conflicts with
--no-validate. render, render-tree, diff, diff-tree, apply, and
publish-tree support file exports. Only render-tree supports PATH=-; its
informational messages then go to stderr so stdout contains exactly one report.
Plain render reserves stdout for manifests.
--no-validation-stderr suppresses human findings and the final validation
summary. Progress and unexpected operational errors remain visible. Exported
reports retain their findings regardless of this switch.
Reports are written atomically on successful or failing validation. Keep them outside managed render trees, vendor directories, and source files. Duplicate or conflicting output destinations are rejected. A report write failure fails the command and blocks diff, apply, and publication. Rendering still attempts to produce inspectable manifests.
JSON version 1
Section titled “JSON version 1”The envelope contains version, status, complete, summary, destinations,
resources, and operationErrors. JSON contains every resource result,
including valid and skipped resources. Ordering is deterministic by destination,
rendered path, document number, List item indexes, resource identity, and finding
path/message. Reports contain no ANSI formatting or elapsed-time fields.
Each resource result contains:
validatoranddestination.resource:apiVersion,kind, nullablenamespace, and nullablename.renderedLocation: target-relativepath, one-baseddocument, anditemscontaining zero-based indexes for nested Lists. File-based operations identify the validated stream as<rendered>; authoring paths are in provenance.status:valid,invalid,error,skipped, ornotChecked.findings: a list of nullable validator instancepathand human-readablemessagepairs. An empty path identifies the resource root; null means the validator supplied no field location.provenance: ordered frames tagged bytype.sourcehaspathanddocument;resourcehasidentity;generatedhasoperation;remotehasrepositoryand resolvedrevision. An empty list means unavailable.schemaOrigin: null when unresolved, or a tagged origin.capturedcontainsclusteranddigest;desiredcontainscrdanddigest;localcontainspathanddigest;builtincontainsurland nullabledigest. Digests are SHA-256 over the schema’s RFC 8785 canonical JSON; built-in URLs retain the registry commit and selected Kubernetes version.
summary counts resources as valid, invalid, errors, skipped, and
notChecked. Multiple findings on one resource count as one invalid resource.
Operation errors have an optional destination and a message; they are separate
from resource error counts.
The overall status is valid, invalid, or error. A resource error or
incomplete operation produces error; otherwise any invalid resource produces
invalid. complete means every input resource has a trustworthy outcome and
there are no operation errors. Schema preparation failures, malformed tool
responses, and timeouts stop validation, preserve completed destinations, and
leave unchecked resources as notChecked. Ordinary invalid resources do not
stop later destinations. Skipped Lists appear as one skipped resource with their
contents excluded.
Failures before a validation input inventory can be constructed remain ordinary command errors and do not produce a validation report. Exporting findings does not change the validation exit status or authorize deployment.