Skip to content

Validating Example YAML

make lint-examples validates every example YAML and every applyable doc YAML block against the live CRD schemas via kubectl apply --dry-run=server. Drift between examples and the current *_types.go definitions surfaces as a CI failure.

What gets checked

Source Selection rule
examples/**/*.yaml Every file (multi-document files are applied as one stream so forward references resolve)
docs/**/*.md Every ```yaml fenced block that (a) contains apiVersion: + kind:, AND (b) sits inside a === "YAML" Material for MkDocs tab — i.e. is authored as applyable

Blocks lacking apiVersion: (schema fragments, response payloads, front-matter examples, partial snippets) are skipped automatically.

Cluster prerequisites

The lint needs a cluster with our CRDs installed:

hack/setup-kind.sh        # one-time: create the local kind cluster
hack/install-crds.sh      # apply config/crd/bases/ to the cluster

If no cluster is reachable, the lint skips with a warning and exits 0, so make verify stays green on machines without a kind cluster. Skip messages name the kubeconfig context that was probed, so a wrong-context skip is diagnosable. CI (see .github/workflows/lint-examples.yml) sets up Kind and enforces LINT_EXAMPLES_REQUIRE_CLUSTER=1 so a missing cluster is a failure there.

If the cluster's CRDs are older than config/crd/bases/, examples using newer fields fail with unknown field errors. Rerun hack/install-crds.sh (or make install) to refresh them.

Opt-out marker

Some YAML blocks intentionally show invalid or aspirational shapes (reference snippets with placeholders, third-party CRDs not installed in the lint cluster, documented anti-patterns). Mark these so the lint skips them:

Source Marker
YAML file in examples/ # lint-examples:allow: <reason or issue link> on any line in the first 20
Doc YAML block <!-- lint-examples:allow: <reason or issue link> --> on a line within four lines above the fenced block

The marker must state a reason or link a tracking issue. It is an exception for teaching snippets, and it is no blanket waiver. Every opt-out is an admission that the lint cannot enforce the contract at that location, and should be revisited when the underlying constraint changes.

Examples currently in use:

  • Reference snippet with ... placeholders (docs/STYLE-GUIDE.md)
  • Third-party CRD not present in the lint cluster (examples/newark-plant/19-historian-cnpg-cluster.yaml)
  • Stale schema with a tracked migration issue (docs/control-modules.md, docs/recipes.md)

When schema validation passes but resource references don't

kubectl --dry-run=server validates the schema before checking cross-resource references (namespace existence, RBAC, webhooks). A "namespace not found" error from the server means the schema validation already succeeded. The lint treats it as a pass, because the goal here is CRD schema drift detection, with transitive resource graph validation out of scope. The doc text is expected to direct the reader to apply prerequisites in order.

Writing new examples

  • Use pharma-realistic naming (granulator-1, site-newark-plant, reactor-temp-high), per STYLE-GUIDE.md.
  • For multi-resource walkthroughs, keep each resource in its own numbered file under examples/<scenario>/ so apply order is obvious.
  • Cross-check against config/crd/bases/<group>_<plural>.yaml after any kubebuilder marker change. The lint will catch drift on the next push, but it's cheaper to catch it locally.
  • New CRD types: add a minimal example under examples/ (per CLAUDE.md), and that example becomes the lint's reference shape.

Running locally

make lint-examples                            # full check (skips if no cluster)
LINT_EXAMPLES_VERBOSE=1 make lint-examples    # show skipped blocks + reasons
LINT_EXAMPLES_ONLY_FILES=1 make lint-examples # skip the doc-block scan
LINT_EXAMPLES_ONLY_DOCS=1  make lint-examples # skip the examples/ scan

The script and Python helper live at scripts/lint-examples.sh and hack/lint-examples.py.