Module Variables
This page shows you how to pass your own variables to a TerraformCluster,
TerraformMachine or TerraformMachinePool’s module: instance sizes,
CIDRs, SSH keys, database passwords, anything besides the contract
inputs CAPTF sets itself.
Before you begin
- The module declares each variable you set, with a default so it still
plans when nobody sets the variable
(
contract/v1alpha1/common.md). A variable the module does not declare fails the apply. - To pass a variable from a ConfigMap or Secret, you need permission to create and label one in the object’s namespace.
Set an inline variable
Add the variable to spec.variables (or spec.template.spec.variables on a
*Template kind), a JSON object:
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
kind: TerraformMachineTemplate
metadata:
name: workers-large
spec:
template:
spec:
source:
image: ghcr.io/example/aws-machine:v1.4.0
variables:
instance_type: m6i.xlarge
root_volume_gib: 100
subnet_ids: [subnet-0a1, subnet-0b2]
Each key becomes a named argument of the module: the generated root
declares it without a type and passes it through, so the module’s own
declared type converts the value (a ConfigMap string "100" becomes the
number 100 for a number variable).
Set a variable from a ConfigMap or Secret
-
Create the ConfigMap or Secret in the object’s namespace, and label it so CAPTF is allowed to read it:
kubectl create configmap aws-sizing -n <namespace> \ --from-literal=instance_type=m6i.xlarge kubectl label configmap aws-sizing -n <namespace> captf.io/variables=true<namespace>is the object’s own namespace:variablesFromnever reaches across namespaces. This Secret is unrelated to the credentials Secret aTerraformClusterIdentitynames: the two are separate mechanisms with separate labels and separate readers (see Identities and Credentials).The manager watches only labeled ConfigMaps and Secrets, and reads their data straight from the API server when it renders a Job; it never caches it. The label is an explicit opt-in, part of the trust boundary: an object cannot read an arbitrary Secret of its namespace just by naming it.
-
Reference it from
spec.variablesFrom:spec: variablesFrom: - configMapRef: name: aws-sizing - secretRef: name: db-credentials format: JSON - configMapRef: name: team-overrides optional: trueEvery data key of a labeled source becomes a variable of that name. Set
optional: trueon a source that may not exist yet: a missing or unlabeled required source blocks the object atDependenciesReady=False/VariablesSourceNotFoundand no Job starts; an optional one just contributes nothing. Every value, including a Secret’s, must be UTF-8; a ConfigMap’sbinaryDatais read the same asdata. -
Confirm:
DependenciesReadyturnsTrueonce every required source resolves, and the object’s next Job carries the variable. Inspect the renderedterraform.tfvars.jsonof a live object as described in Job inputs.
Merge order and formats
spec.variablesFrom, in list order: every source’s keys become variables, and a later source wins on a key it shares with an earlier one.spec.variables(inline) wins over every source.
A source’s format decides how its values are read:
String(the default): each value is passed as a string. Use it for scalars; the module’s declared type converts"3","true"and"0.5".JSON: each value is parsed as JSON, for lists, maps and objects (["a","b"],{"team":"a"}). A value that is not valid JSON holds the object atDependenciesReady=False/VariablesInvalid, naming the source and the key, never the value.
Names and limits
A variable name is a Terraform identifier: ^[a-zA-Z_][a-zA-Z0-9_-]*$.
Rejected, whatever the format:
- a name starting with
captf_; - a contract input of the object’s role — a machine cannot set
machine_name, but a cluster can, sincemachine_nameis not one of its inputs (contract reference); - a module meta-argument:
source,version,providers,count,for_each,depends_on,lifecycle,locals.
spec.variables holds at most 256 keys, and when set, at least one; the
admission webhook rejects a bad inline key or too many of them at once, and
also rejects a variablesFrom entry that names both a ConfigMap and a
Secret, or neither. A bad key in a referenced source is caught only when
the controller reads it, as VariablesInvalid naming the key.
spec.variablesFrom holds at most 16 sources.
Variables count toward the rendered-inputs limit shared with every other
input (1,000,000 bytes): past it the apply is refused with
ApplyJobSucceeded=False/InputsTooLarge
(Size limits). They are also
part of the inputs hash that decides whether a
mutable object’s next reconcile re-applies.
Sensitivity
A variable’s winning value (after the merge above) is declared
sensitive = true in the generated root when it came from a Secret, so
Terraform and OpenTofu redact it in plan and apply output. A value that
ends up inline or from a ConfigMap is not sensitive, even if an earlier,
losing source was a Secret, and even if the module’s own declaration marks
its variable sensitive. Sensitive or not, every variable is still stored
like any other input: in the object’s inputs Secrets and in the state
(Secrets, Terraform
state).
What a change does per kind
TerraformCluster: the variables are part of the inputs hash. Editingspec.variables,spec.variablesFromor the data of a referenced source re-applies the module, guarded like any other input change (Plan approval). The controller re-reads every referenced source on each reconcile, and a change to a labeled source it references wakes it.TerraformMachine:spec.variablesandspec.variablesFromare immutable. The controller reads the sources until the machine is provisioned; after that it never reads them again, and the machine’s destroy, refresh and drift runs use the variables already pinned in its durable inputs Secret. Editing a referenced ConfigMap or Secret therefore affects only machines created afterward: roll the MachineDeployment, or bump the template, to replace existing ones.TerraformMachinePool:spec.variablesandspec.variablesFromare mutable, and the sources are re-read on every reconcile for the pool’s whole life, the same as a TerraformCluster; a change to a labeled source it references wakes it immediately, the same way. The variables are part of the inputs hash, but the resulting re-apply is never guarded: a TerraformMachinePool has noapplyPolicy(Machine pools).- Templates:
spec.template.specof a*Templatekind, so itsvariablesandvariablesFromtoo, is immutable like the rest of the template (The Kinds); a ClusterClass topology patch onspec.template.spec.variablesrolls out through a new template (Templates and ClusterClass). spec.defaultson aTerraformClusterdoes not cover variables: a machine or pool without its ownidentityRef,jobsordriftinherits the cluster’s, but variables are never inherited, so set them on the machine, pool or their templates directly.clusterctl movenever carries avariablesFromsource: CAPTF puts no owner reference on a ConfigMap or Secret it reads. ATerraformClusterorTerraformMachinePool, or a machine not yet provisioned, waits atVariablesSourceNotFoundon the target until you recreate the source there (clusterctl move).
Troubleshooting
| Symptom | Reason | Fix |
|---|---|---|
DependenciesReady=False/VariablesSourceNotFound | A required variablesFrom source is missing, or not labeled captf.io/variables=true | Create or label the ConfigMap or Secret, or set optional: true |
DependenciesReady=False/VariablesInvalid | A source’s key is not a Terraform identifier, is reserved, or (format JSON) its value is not valid JSON | Rename or remove the key, or fix the source’s format |
Admission rejects spec.variables | Not a JSON object, an inline key is reserved or not a Terraform identifier, or there are more than 256 keys | Fix or trim the key named in the error |
ApplyJobSucceeded=False/ApplyFailed, status.lastRun.error.summary has Unsupported argument or Extraneous JSON object property | The module does not declare a variable you set | Add the variable to the module, or stop setting it |
See conditions.md for
every DependenciesReady reason.
See also
- Identities and Credentials — the separate Secret mechanism for cloud credentials.
common.md— how a module declares a user variable.- Job inputs — the full render pipeline and the inputs hash.
- Templates and ClusterClass — patching a variable per Cluster.
- Plan approval — the destructive-plan guard a TerraformCluster variable change is subject to.
reference/api.md— the full field list forvariablesandvariablesFrom.