Skip to content

Known Limitations

This page lists what CAPTF does not do, or does with a catch, as the code stands today. Each item says what the limit is, what follows from it, and where the detail is. A limit that has a workaround says so. For versions and what has and has not been tested, see Compatibility.

Maturity

  • Pre-alpha. No release is published. Every API kind is v1alpha1, and the module contract is v1alpha1 and provisional: it may change before a real module has provisioned a cluster with it. See Project status.
  • No end-to-end run against a live management cluster has happened. The tests are unit tests that mock the Kubernetes API and Job execution; CI runs unit tests, lint and the offline verifications. Anything that needs a real cluster, a real cloud or a real provider is untested: see Compatibility.

Approvals and gates

  • Only the TerraformCluster is fully gated. applyPolicy: Manual and the destructive-plan guard exist on the cluster. A machine’s apply never waits for approval. A pool’s apply is guarded in one case only: when it renders changed cluster exports. See What is guarded.
  • A pool’s exports guard has limits. A destructive pool apply of changed exports is held: the pool keeps applying with the exports of its last successful apply until the change is approved. The guard needs the record of the last applied exports, which shares the durable Secret’s budget with the rendered inputs, and clusterctl move re-runs a blocked plan once. Keep shared and destructive infrastructure in the cluster module, keep exports stable, and use prevent_destroy on what must not go. See Machine pools and Cluster outputs reach pools and machines.
  • An approval binds a plan, not the apply. It names the plan’s hash and the apply must plan the same again; it does not promise what the provider does. See An approval binds a plan.

Security

A module image can read every Secret in its namespace and forge a plan hash

  • The module image can read the plan key. The per-object key that makes plan hashes unforgeable by accident is mounted read-only in the module’s container for plan and approved-apply Jobs. A hostile module can read it and forge a plan hash. It protects against drift, not against the module. See The plan key is readable by the module.
  • The runner can read and write every Secret in its namespace. RBAC cannot scope the state backend’s access. Namespaces are the only tenant boundary. See Multi-Tenancy.
  • No encryption at rest of its own. State, inputs and credential mirrors are Kubernetes Secrets. See No encryption at rest.

State

  • OpenTofu state encryption is unsupported. An encrypted state reads as StateReadable=False/StateEncrypted, and no backup of it is ever taken. See Unreadable State.
  • The state file version must be 4. Another version reads as StateCorrupt.
  • Backups are not disaster recovery. They are in the same namespace, owned by the object, and rotated out after --state-backups. See Disaster Recovery.
  • Owner references are repaired on the next reconcile, not at once. The controller owns a state chunk, backup, durable inputs Secret and plan key again on the next reconcile that finds no Job running. The gaps are narrow: nothing is re-owned while the object is paused (so clusterctl move is not raced), state chunks wait while a Job holds the run lease, and a restored Secret that still names an old UID can be garbage-collected before the first reconcile. See Disaster Recovery.

Jobs and leases

  • The lease grace gap. If a TerraformCluster’s inputs change while it waits for machine operations, the old leases are held by a Job that will never exist, and the new Job waits out the one-minute grace. It resolves itself. See The known gap.
  • No flag caps running Jobs. The concurrency flags cap reconciles per kind, not Jobs. The bounds are one Job per object and your quotas.
  • No quota-specific condition. A ResourceQuota refusal shows as a reconcile error or a Job that never starts. See Production Readiness.
  • A single manager watches one namespace or all of them. The leader-election lease name is fixed, so per-namespace manager instances do not work; use --watch-filter. See Namespace scoping.

Deletion and move

  • A paused object’s deletion waits. A paused object, or one under a paused Cluster, never runs a destroy. Deleting says so. This is by design, so clusterctl move can delete source objects. See Pause stops a deletion.
  • A moved object in a deleted namespace looks never-applied. Status does not move, and the captf.io/applied marker is on a Secret the namespace deletion removes. Its finalizer then comes off with nothing destroyed. See The known limit.
  • The credential source Secret does not move. Copy it to the target yourself. See clusterctl move.

Machine pools

  • The Kubernetes Cluster Autoscaler is unsupported on pools. The controller supports cloud-native autoscaling in the module, driven by the pool’s min and max annotations, and writes the observed replicas back to MachinePool.spec.replicas. The Cluster Autoscaler’s clusterapi provider needs MachinePool Machines and drains nodes before it scales down, and neither exists. Its changes to spec.replicas are overwritten by the write-back. See Machine Pools.
  • MachinePool Machines are unsupported. Pool instances have no Machine objects, so a MachineHealthCheck never selects them.
  • The bootstrap Secret is not watched. A rotated token reaches the pool at its next reconcile, at the latest one membership-refresh interval later.