OpenTofu Modules & Testing
SkillDev toolsWrite OpenTofu modules and tests for homelab infrastructure (infrastructure/modules/).
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the OpenTofu Modules & Testing skill
What this skill tells your AI
The instructions your AI receives, as published by ionfury/homelab in .claude/skills/opentofu-modules/SKILL.md and read by ahel’s review.
Write OpenTofu modules and tests for the homelab infrastructure. Modules live in infrastructure/modules/, tests in infrastructure/modules/<name>/tests/. Run tests with task tg:test-<module> (e.g., task tg:test-config); format with task tg:fmt. OpenTofu version is pinned in .opentofu-version.
Module Structure
Every module MUST have:
infrastructure/modules/<name>/
├── variables.tf # Input definitions with descriptions and validations
├── main.tf # Primary resources and locals
├── outputs.tf # Output definitions
├── versions.tf # Provider and OpenTofu version constraints
└── tests/ # Test directory
└── *.tftest.hcl
Test File Structure
Use .tftest.hcl extension. Define top-level variables for defaults inherited by all run blocks.
# 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
Always use command = plan — validates configuration without creating resources. Only include variables in run blocks when they differ from top-level defaults; inherited variables need not be repeated. Assert against module outputs, not internal resources.
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]
}
Test Organization
Organize tests by concern:
plan.tftest.hcl- Basic structure and output validationvalidation.tftest.hcl- Input validation rulesfeature_<name>.tftest.hcl- Feature flag behavioredge_cases.tftest.hcl- Boundary conditions
Detailed Reference
For OpenTofu testing syntax, mock providers, and advanced patterns, see: references/opentofu-testing.md
Signals
- GitHub stars
- 25
- Forks
- 3
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
opentofu-modules- Source
- github.com/ionfury/homelab