Importing Existing YAML
Show the whole prompt
Read https://intentius.io/chant/lexicons/k8s/importing-yaml/.
Import the Kubernetes manifests I name with `npx chant import <file> --lexicon k8s --output src`, one directory per manifest file.
Run `npx chant build src --output dist/manifests.yaml` and `npx chant lint src`, compare the kinds and names with the originals, and fix the generated source until they match.
Open a pull request with src/, and list every warning the import printed in its description.
Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.Optional: hand this page to your coding agent
Read https://intentius.io/chant/lexicons/k8s/importing-yaml/.Import the Kubernetes manifests I name with `npx chant import <file> --lexicon k8s --output src`, one directory per manifest file.Run `npx chant build src --output dist/manifests.yaml` and `npx chant lint src`, compare the kinds and names with the originals, and fix the generated source until they match.Open a pull request with src/, and list every warning the import printed in its description.Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.chant import reads Kubernetes YAML and writes TypeScript, so you can move existing manifests into chant.
How it works
Section titled “How it works”Input YAML → parse → generate TypeScript → export typed resourcesThe importer reads multi-document YAML, identifies each resource’s apiVersion and kind, and generates the corresponding typed constructor call. The output is a valid .k8s.ts file you can immediately build with chant build.
Collector configs, rule groups, Alertmanager configs and dashboards inside manifests
Section titled “Collector configs, rule groups, Alertmanager configs and dashboards inside manifests”chant import hands content that another lexicon owns to that lexicon, when it is installed:
| In the manifest | Imported as |
|---|---|
A ConfigMap value holding an OpenTelemetry Collector config (any key: config.yaml, the Helm chart’s relay) | otel components and pipelines, and collectorYaml([...]) in the ConfigMap |
| A ConfigMap value holding a Prometheus rule file | prometheus RuleGroups (an Slo’s group as that Slo), and ruleFileYaml([...]) in the ConfigMap |
A PrometheusRule’s spec.groups | prometheus RuleGroups, listed in spec.groups |
A ConfigMap value holding an alertmanager.yml, such as the one the alertmanager Helm chart renders | prometheus receivers, time intervals, the root Route, inhibit rules and AlertmanagerSettings, and alertmanagerYaml([...]) in the ConfigMap |
A ConfigMap value holding a prometheus.yml, such as the one the Prometheus Helm chart renders | prometheus ScrapeConfigs and PrometheusConfig, and prometheusConfigYaml([...]) in the ConfigMap |
A ConfigMap value holding classic Grafana dashboard JSON, such as a dashboard labelled grafana_dashboard for the Grafana sidecar | a grafana Dashboard, and dashboardJson(dashboard) in the ConfigMap |
A Grafana Operator GrafanaDashboard’s spec.json | a grafana Dashboard, and dashboardJson(dashboard) in spec.json |
Each is written in a directory named after the resource (and the key, when the ConfigMap has several), with the owner’s usual modules, and the k8s module imports what it references. The workload still mounts the ConfigMap by name, and chant build renders the same content back into it. The content comes back as the owner’s serializer writes it: an alertmanager.yml has its receivers sorted by name, a prometheus.yml has its scrape jobs sorted by name, and Alertmanager’s deprecated match spellings rewritten as matchers, as a standalone chant import alertmanager.yml does.
A v2 Grafana dashboard (dashboard.grafana.app/v2, Grafana 13’s default export) in a ConfigMap stays a string, with a warning. The grafana lexicon reads v2, but it builds classic (v1) JSON, so importing it would turn the ConfigMap’s v2 dashboard into a v1 one on the next build. To manage it as a typed dashboard, import the JSON on its own with the grafana lexicon (see its v2 dashboards) and put dashboardJson(dashboard) in the ConfigMap yourself.
The k8s lexicon does not depend on those lexicons for this. Core finds the owner at run time. If it is not installed, the value stays a string and the import warns, naming the package to install. Other ConfigMap values, such as a Loki or Tempo config, stay strings with no warning.
Running the import roundtrip
Section titled “Running the import roundtrip”From the lexicons/k8s directory:
# Full roundtrip — clones kubernetes/examples, imports, serializes, comparesjust full-roundtrip
# Skip clone if repo is already cachedjust full-roundtrip --skip-clone
# Verbose output + filter to a specific manifestjust full-roundtrip --skip-clone --verbose --manifest guestbook
# Skip the serialize phase (parse + generate only)just full-roundtrip --skip-clone --skip-serializeParse-only mode
Section titled “Parse-only mode”For quick validation without the full serialize cycle:
just import-samples --skip-clone --verbose
# Filter to a specific manifestjust import-samples --skip-clone --verbose --manifest guestbookThis generates TypeScript from the YAML and skips the round-trip comparison.
What “pass” means
Section titled “What “pass” means”A passing roundtrip means the serialized YAML, when re-parsed, produces the same number of resources with matching kind values. Exact YAML comparison is intentionally skipped — key ordering, quoting, and comments differ between input and output.
Validating against a cluster
Section titled “Validating against a cluster”To verify the serialized output is valid Kubernetes YAML, apply it to a local k3d cluster:
# Create a k3d cluster, apply serialized manifests, verify, tear downjust k3d-validate
# Keep the cluster for manual inspectionjust k3d-validate --keep-cluster
# Reuse an existing clusterjust k3d-validate --reuse-cluster --verboseSee Testing & Validation for full details on the k3d validation workflow.
Using YAML without converting it
Section titled “Using YAML without converting it”Converting to typed source is not always worth it. A vendored operator bundle, a CRD bundle from an upstream release, or an overlay tree another team maintains can join the build as it is. Its documents become entities like any declared resource: the build output carries them with default labels and ownership stamped, post-synth checks read them, and lifecycle diff --live compares them with the cluster.
Including a file or URL
Section titled “Including a file or URL”k8sInclude declares a multi-document YAML file or a pinned URL:
import { k8sInclude } from "@intentius/chant-lexicon-k8s";
// Relative to the project root (the directory holding chant.config.ts).export const legacy = k8sInclude({ source: "manifests/legacy.yaml" });
// A URL must carry the sha256 digest of the bytes you reviewed.export const certManager = k8sInclude({ source: "https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml", digest: "sha256:<64 hex digits>",});Compute a URL’s digest from a copy you have looked at, for example curl -sL <url> | shasum -a 256, and prefix it with sha256:. A URL without a digest is refused when the module loads.
The file is read, or the URL fetched, during chant build. Fetched bytes must match the digest or the build fails with both digests in the message. Matching bytes are cached under ~/.chant/includes/, named by digest, so later builds read the cache and need no network. Set CHANT_INCLUDE_CACHE_ROOT to move the cache, for example to a directory a CI job restores or an air-gapped build is handed. A digest on a local file is optional; when set, the file must match it too.
Each document becomes an entity named <export name>/<kind><Name>, for example certManager/deploymentCertManagerWebhook. Two documents with the same kind and name are told apart by namespace, then by a number in document order, so the names are the same on every build. Every included document carries a chant.intentius.io/include-source annotation naming its file or URL.
Empty documents (a stray ---) are skipped. A missing file, a failed fetch, a file with no documents, and a document without apiVersion and kind each fail the build with the path or URL in the message.
Kustomize build roots
Section titled “Kustomize build roots”k8s.kustomize.roots in chant.config.ts names kustomization directories to render with kustomize build (or kubectl kustomize when the standalone binary is missing) during the build. Paths are relative to the project root. The rendered documents become entities the same way included ones do, named <root>/<kind><Name> and annotated with chant.intentius.io/kustomize-root.
export default { lexicons: ["k8s"], k8s: { kustomize: { roots: ["overlays/prod"] }, },};Patching included and rendered documents
Section titled “Patching included and rendered documents”A file or chart sometimes has no setting for the field you need. Patches change included and rendered documents before they become entities, so the patched form is what the build emits, what ownership stamping and post-synth checks see, and what lifecycle diff --live compares with the cluster.
A patch selects documents by kind and name, with optional namespace and apiVersion, and carries either a jsonPatch (RFC 6902 operations: add, remove, replace, move, copy, test) or a merge (RFC 7386: objects merge, arrays are replaced whole, null deletes a key).
import { k8sInclude, renderedPatch } from "@intentius/chant-lexicon-k8s";
export const certManager = k8sInclude({ source: "https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml", digest: "sha256:<64 hex digits>", patches: [ renderedPatch({ kind: "Deployment", name: "cert-manager", namespace: "cert-manager", merge: { spec: { replicas: 2 } }, }), { kind: "Deployment", name: "cert-manager-webhook", jsonPatch: [ { op: "test", path: "/spec/template/spec/containers/0/name", value: "cert-manager-webhook" }, { op: "add", path: "/spec/template/spec/containers/0/args/-", value: "--v=4" }, ], }, ],});A plain object works anywhere a patch is accepted. Wrapping it in renderedPatch checks a merge body against the props of the lexicon’s class for that kind.
The same patches go on a kustomize root by writing the entry as an object:
k8s: { kustomize: { roots: [ { path: "overlays/prod", patches: [{ kind: "Deployment", name: "web", merge: { spec: { replicas: 3 } } }] }, ], },},and on a Helm render through HelmRender’s patches prop (see the Helm lexicon’s best practices).
Patches apply in order, and each applies to every document its selector matches, so a later patch sees an earlier one’s result. Leaving out namespace matches documents in any namespace. A test operation fails the build when the value is not what you expected, which pins the assumption a patch rests on. A selector that matches no document also fails the build, listing the documents of that kind that are present; this is how a renamed resource in an upgraded bundle or chart gets noticed instead of the patch silently no longer applying.
Limitations
Section titled “Limitations”The import pipeline does not support:
- Helm charts — template syntax (
{{ .Values.x }}) is not valid YAML - Kustomize overlays — overlays are processed by kustomize before producing YAML; to build one as it is, use a kustomize build root
- Custom Resource Definitions (CRDs) — only built-in K8s resource types are recognized
- Cloud-specific volumes — provider-specific volume types (awsElasticBlockStore, gcePersistentDisk) are parsed but excluded from k3d cluster validation (k3d doesn’t provide cloud volume drivers)