OpenTofu Modules & Testing
Write OpenTofu modules and tests for the homelab infrastructure. Modules live in
infrastructure/modules/
, tests in
infrastructure/modules/
Run tests for a module
task tg:test- < module
e.g., task tg:test-config
Format all HCL
task tg:fmt
Version pinned in .opentofu-version (currently 1.11.2)
Module Structure
Every module MUST have:
infrastructure/modules/
Top-level variables set defaults for ALL run blocks
variables { name = "test-cluster" features = [ "gateway-api" , "longhorn" ] networking = { id = 1 internal_tld = "internal.test.local"
... other required fields
}
Default machine - inherited unless overridden
machines
{ node1 = { cluster = "test-cluster" type = "controlplane" install = { selector = "disk.model = *" } interfaces = [ { id = "eth0" hardwareAddr = "aa:bb:cc:dd:ee:01" addresses = [ { ip = "192.168.10.101" } ] } ] } } } run "descriptive_test_name" { command = plan
Use plan mode - no real resources created
variables { features = [ "prometheus" ]
Only override what differs
} assert { condition = output.some_value = = "expected" error_message = "Descriptive failure message" } } Key Patterns Use command = plan Always use plan mode for tests. This validates configuration without creating resources. Variable Inheritance Only include variables in run blocks when they differ from defaults. Minimizes duplication.
CORRECT: Override only what changes
run "feature_enabled" { command = plan variables { features = [ "prometheus" ] } assert { ... } }
AVOID: Repeating all variables
run "feature_enabled" { command = plan variables { name = "test-cluster"
Unnecessary - inherited
features
[ "prometheus" ] machines = { ... }
Unnecessary - inherited
} } Assert Against Outputs Reference module outputs in assertions, not internal resources. assert { condition = length(output.machines) = = 2 error_message = "Expected 2 machines" } assert { condition = output.talos.kubernetes_version = = "1.32.0" error_message = "Version mismatch" } Test Feature Flags Test both enabled and disabled states: run "feature_enabled" { command = plan variables { features = [ "longhorn" ] } assert { condition = alltrue( [ for m in output.talos.talos_machines : contains(m.install.extensions, "iscsi-tools" ) ] ) error_message = "Extension should be added when feature enabled" } } run "feature_disabled" { command = plan variables { features = [ ] } assert { condition = alltrue( [ for m in output.talos.talos_machines : !contains(m.install.extensions, "iscsi-tools" ) ] ) error_message = "Extension should not be present without feature" } } Test Validations Use expect_failures to verify variable validation rules: run "invalid_version_rejected" { command = plan variables { versions = { talos = "1.9.0"
Missing v prefix - should fail
...
} } expect_failures = [ var.versions ] } Common Assertions
Check length
condition
length(output.items)
= 3
Check key exists
condition
contains(keys(output.map), "expected_key" )
Check value in list
condition
contains(output.list, "expected_value" )
Check string contains
condition
strcontains(output.config, "expected_substring" )
Check all items match
condition
alltrue( [ for item in output.list : item.enabled = = true ] )
Check any item matches
condition
anytrue( [ for item in output.list : item.name = = "target" ] )
Nested check with labels/annotations
condition
anytrue(
[
for label in output.machines
[
"node1"
]
.labels :
label.key
=
=
"expected-label"
&& label.value
=
=
"expected-value"
]
)
Test Organization
Organize tests by concern:
plan.tftest.hcl
- Basic structure and output validation
validation.tftest.hcl
- Input validation rules
feature_