Retain and Adopt¶
Deleting a TerraformCluster, TerraformMachine or TerraformMachinePool normally destroys what its module created. spec.deletionPolicy: Retain deletes the object without a destroy and keeps what CAPTF knows about the infrastructure, so a later object can manage it again. This page covers what Retain keeps, what it releases, and how a new object adopts the retained state, or refuses to until you say so.
Setting the policy¶
spec.deletionPolicy is Destroy or Retain. It is mutable at any time, also after the deletion has started:
kubectl patch terraformmachine <name> -n <namespace> --type merge \
-p '{"spec":{"deletionPolicy":"Retain"}}'
An unset policy inherits, like other operational policy (the inheritance rule): a machine or pool takes its cluster’s spec.defaults.deletionPolicy, else the TerraformCluster’s own spec.deletionPolicy, else Destroy. A TerraformCluster uses only its own value. Machine templates are immutable, so set the policy for a cluster’s machines on the TerraformCluster rather than in the template: that needs no rollout.
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
kind: TerraformCluster
spec:
deletionPolicy: Retain
The fields are documented in Common Fields.
What Retain keeps¶
On deletion with Retain, no destroy Job runs. Once no Job of the object is running, the controller:
| What | Retain does |
|---|---|
| The state Secrets, every chunk | Kept |
| The state backups | Kept |
| The durable and applied inputs Secrets | Kept |
| The state lock Lease | Deleted |
| The plan key, the run and write leases | Deleted |
| The object’s place among the credential mirror’s owners | Dropped, as on every deletion |
| The finalizer | Removed |
Each kept Secret loses its owner reference to the object, so Kubernetes does not garbage-collect it with the object, and gets the label captf.io/retained-from-uid with the object’s metadata.uid. The backend’s labels stay, so Terraform and OpenTofu still read the state, and the clusterctl.cluster.x-k8s.io/move label stays, so a later clusterctl move carries the Secrets along. A Normal event InfrastructureRetained counts what was kept.
The cloud infrastructure keeps running and is managed by nothing until an object adopts it. Every step is idempotent: a pass cut short is finished by the next.
What Retain releases¶
Retain is decided before any restore or destroy, as soon as no Job runs. Setting it on a deletion that is stuck releases it, whatever holds it:
- the state is lost or unreadable (a held deletion);
- the last destroy failed;
- the destroy cannot start: the inputs Secrets (durable and applied) are gone, the identity no longer allows the namespace or no longer exists, or the runner credentials cannot be prepared.
The messages of those conditions name spec.deletionPolicy: Retain as the way out. Unlike stripping the finalizer by hand, nothing is lost: the state that exists, its backups and the inputs stay, ready to be adopted or restored.
Retain never destroys, even when the destroy would succeed
With Retain set, a deletion runs no destroy at all. Remove the infrastructure through the cloud provider, or adopt it and delete the adopting object with Destroy, if you want it gone.
A paused object never retains, as it never destroys, and a TerraformCluster still waits for its machines and pools to go first.
A recreated object holds¶
A state Secret’s name and a backup’s selector come from the object’s namespace, kind and name, not its UID. So a new object of the same kind, namespace and name finds the Secrets an earlier one retained. It never adopts them silently: they describe infrastructure that may still be running, and the new object may be meant to build something else.
Instead the object holds: StateReadable is False with reason RetainedStateFound, a RetainedStateFound Warning event fires, and the message names the retaining UID. While it holds, no Job runs, no state is adopted, backed up or read into status, and none of the retained Secrets gets an owner reference. The hold has two exits: adopt, or discard.
Adopting retained state¶
Set spec.adoptRetainedState: true on the new object:
The controller removes the captf.io/retained-from-uid label from every retained Secret it found and emits RetainedStateAdopted. On the next reconcile it owns them, reads the state as the object’s own and carries on from there: a machine whose state reads becomes provisioned without a new apply, a cluster or pool applies only if its inputs differ from the state’s. The field is never inherited, and it stays set; leave it or clear it as you like.
Adopt with the same inputs
The adopted state was written by the earlier object’s inputs. A TerraformCluster or TerraformMachinePool whose spec differs applies the difference, guarded as any change is. A TerraformMachine is immutable and keeps the adopted inputs records, image and identity.
Discarding retained state¶
To start afresh instead, delete the retained Secrets before or after you create the new object:
The infrastructure they described keeps running, unmanaged. Delete it through the cloud provider.
Deleting while held¶
Deleting an object that holds on another object’s retained state removes its finalizer at once, without a Job, whatever its own deletionPolicy. The object never created anything, and the retained Secrets are left exactly as they were: never deleted, owned or relabeled. The FinalizerRemoved event names the retaining UID.