Compatibility

choudoufu live-check

No cluster needed. On a root that declares Kubernetes resources it prints one of two things per block.

Plans today

Every type whose schema carries Kubernetes object metadata: a metadata block with a name, a uid, a labels map and, for a namespaced kind, a namespace. That is the object-metadata rule (#1064): the identity is NAMESPACE/NAME, or NAME for a cluster-scoped kind, read from the block, and no type needs a row of its own to say so. The four types that had ratified rows before the rule existed keep them as a check on it. Counted against live/MARKERS.md’s figure for the provider at 3.2.1, that is nearly every resource type the provider ships.

A namespace read from another resource’s metadata, namespace = kubernetes_namespace.x.metadata[0].name, resolves: the name is that resource’s identity attribute, and the reference is followed into the block. So does namespace = kubernetes_namespace_v1.x.id, the shape grafana/quickpizza’s root writes on every namespaced object: the provider sets an object’s id to its own import id (the name for a cluster-scoped kind, NAMESPACE/NAME for a namespaced one), so the rule claims id as an identity attribute and the reference resolves to the parent’s whole identity (#1067).

A kubernetes_secret_v1 whose data keys read sensitive variables plans empty after adoption. The same root found the case where it did not: the provider’s schema marks the whole data map sensitive, the configuration marks each key inside it, and a stateless prior read from the cluster carries only the schema’s mark, so the planner’s sensitivity comparison saw a difference on every run and proposed an in-place update it rendered as unchanged. The comparison now reduces both sides to their minimal cover first: a mark under an already-marked ancestor is not a change.

Custom resources

kubernetes_manifest, and so every custom resource, plans since #1079’s first unit: its identity is the natural key written inside the manifest argument’s own object constructor - apiVersion, kind, metadata.namespace, metadata.name - read key by key without evaluating the manifest (a local or variable the argument is set to is walked the same way) and rendered as the provider’s own import id. A manifest computed some other way, yamldecode(file(...)) or a module output, is refused by name, because the key that names the object is not known until the value exists. Since the ruling’s second unit the object carries the same one label as every built-in type: the plan writes tofu-estate into manifest.metadata.labels on create, merged with any labels the manifest declares, so the admission policy fences it like any other object, and the estate sweep lists every kind the cluster serves, CRDs included, so an object whose block is removed is found by that label and proposed for removal at kubernetes_manifest.orphan_<kind>_<namespace>_<name> (claim 24). A block whose apiVersion and kind the cluster does not serve - the CRD not installed, or served at another version - is refused by name at the plan’s first contact with the cluster, ahead of the provider’s own error: the block, the kind, the apiVersion and the CRD to install (Kubernetes kind not served by the cluster). live-check is offline and cannot ask a cluster, so it does not raise this; a cluster that cannot answer is a warning, never a refusal.

Once the plan exists, every planned create or update of a kubernetes_manifest instance is sent to the API server as the apply would write it, label included, with dryRun=All (#1081, item 3): the server validates it against the kind’s schema, applies its defaults and runs every admission policy, and persists nothing. The answer prints above the plan, one line per object; a rejection is Kubernetes API server rejected the planned object, quoting the server, and the run stops with nothing rendered and nothing applied, on plan and on apply alike. This reaches the manifest shape only. A built-in type’s block is not submitted, because the mapping from metadata[0] and its spec blocks to the API object is the provider’s own and is not reproduced here; an object whose namespace the same plan creates is reported rather than submitted, since the server would answer for the apply’s order and not for the object; a server that cannot answer is a warning. live-check does not ask.

Refused

metadata.generate_name, by name: the server mints the object’s name, so nothing in the configuration states the join key back to the block, which is the one shape that would need the configuration address on the object. Set metadata.name instead. A missing namespace on a namespaced kind is refused rather than defaulted, for the same reason.

live-mv runs on every object-metadata type: a rename within an estate reports nothing to write and exits 0, and -from-estate rewrites the tofu-estate label through the provider under your own credential, so the admission policy judges it like any other write (#1081); a move of a kubernetes_manifest object is refused by name with the equivalent kubectl label.

Still refused: the handful of types whose block is not object metadata (kubernetes_labels, kubernetes_annotations, kubernetes_env, the *_data patch types), which act on an object rather than being one.

helm_release is refused in a live root, and the refusal is the ordinary unadmitted-type one, with or without the provider’s schema (#1081, item 4; ruled 2026-09-13 in #1105). hashicorp/helm 3.2.0 serves the type with no resource identity schema and no object-metadata block (its metadata is a computed record of release facts, with no labels map), so neither admission route reaches it, and live-check says so in those words. It stays refused rather than joining the record rung because a release is not one object this tool creates, reads and deletes: it is a release secret in the release namespace plus whatever the chart rendered, made by a path this tool never sees, and those objects carry the chart’s labels and Helm’s own meta.helm.sh/release-name annotation, not the estate’s label. Helm keeps its estate and this tool keeps its own.

Nothing about Helm is limited in stock OpenTofu, and this fork changes nothing there: a root with no live block installs, upgrades and uninstalls a helm_release exactly as stock does, with the release in the state file and Helm’s own release secret in the cluster. So a team on Helm has two honest choices, and the trade between them is Helm’s lifecycle against ownership:

A chart’s own objects are never the estate’s by accident. Under the ruling an object carrying Helm’s release annotation is controller-held: never swept, never adopted, reported with its release name. That exclusion is #1105’s one unit and is not built yet, so until it lands do not put tofu-estate in a chart’s values: an object carrying it that no block declares is an orphan today, and the sweep will propose removing it from under the release. The opt-in that would bring a release inside the boundary (identity through the release secret, the label written by a post-renderer) is designed on #1105 and not built.

Mixed estates

The common shape is an EKS module that also manages the aws-auth ConfigMap. The AWS resources carry two tags and fall under your IAM; the ConfigMap carries the one label and falls under the cluster’s admission policy, and each substrate’s sweep lists its own. live-check reports that root as not blocked. A root made only of refused types is blocked as a whole, and the report says which root and why.