Adopt
Today
Every type whose schema carries object metadata resolves through one rule
(#1064): its
identity is metadata.namespace and metadata.name, both written in your
configuration, so a plan binds it with nothing stored anywhere. A missing
namespace is refused rather than defaulted, and generate_name is refused
by name. A custom resource, declared through kubernetes_manifest, binds
the same way by the apiVersion, kind, metadata.namespace and
metadata.name written inside its manifest
(#1079); a block
whose kind the cluster does not serve is refused by name, naming the CRD
to install, at the plan’s first contact with the cluster (claim
24).
Every one of them carries the marker: one label, tofu-estate, written on
the create. Strip it with kubectl and the next plan proposes restoring it.
Claim 21 runs that on a
real cluster with a namespace, a ConfigMap, a ServiceAccount and a Service.
Delete one of those blocks from source and the next plan finds the live object by its label, one cluster-wide list per kind, and proposes its removal, with a controller’s copies of the label excluded first (claim 22). And once a cluster admin has installed the one admission policy, every write to one of them is fenced by the label it carries (claim 23; the gate says what that fence does not reach).
From a stock state file
choudoufu live-import -approve
The same bulk path as on AWS (#1073):
the stock state is read once, each object is verified by namespace and
name, and the tofu-estate label is written into metadata.labels
through a labels-only plan and apply. A plan that would also rename the
object, move it between namespaces or change anything outside the labels
map is refused, and so is an object already labelled for another estate.
Then delete the state file and plan: the plan is empty, because every
object is found again by its name and carries the label. The gauntlet’s
reference-k8s estate measures exactly this at its migrate and
test_plan stages.
The marker
On AWS the marker carries the config address, because AWS hands back opaque ids and the tag is the only way from a live object back to a line of configuration. Kubernetes returns the natural key: group, kind, namespace and name, with the name authored in the configuration this fork already parses. So the address does not need to be on the object.
The marker is one label, tofu-estate, and re-binding goes through the
natural key. Measured against the identity golden set, nearly half of real config
addresses are illegal as a label value and a 63-character cap binds at once
on ordinary module-nested shapes; putting the address in a label would need
three or four continuation labels per object and would break the exact-match
condition a policy wants. An estate-only label fits by construction.
Refused, not guessed
generateName lets the server mint the name, which makes the natural key
unknowable before the create. That is the one shape that would drag the
whole address-in-label machinery back in, so it is refused, the same way a
missing namespace is.
Controller-created objects, ReplicaSets and Pods from a Deployment, PVCs
from a volumeClaimTemplate, Jobs from a CronJob, are excluded by a non-empty
metadata.ownerReferences before anything reaches a delete. The author
chose neither the name nor the object.
What Kubernetes does better
metadata.managedFields records which manager last wrote each field, so a
stripped label names who stripped it. Server-side apply refuses a contested
field with a 409 that names the competing manager. Server-side dry run
validates, defaults and runs admission without persisting, which is stronger
evidence than a locally computed plan and something AWS has no equivalent
for; the plan uses it, sending every planned kubernetes_manifest create or
update to the server with dryRun=All and printing the server’s answer
above the plan, and a rejection refuses the plan by name in the server’s
words before anything is applied (claim
24). Built-in types are
not submitted: the mapping from their block shape to the API object is the
provider’s own.