opentofu-modules
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOpenTofu Modules & Testing
OpenTofu模块与测试
Write OpenTofu modules and tests for the homelab infrastructure. Modules live in , tests in .
infrastructure/modules/infrastructure/modules/<name>/tests/为家庭实验室基础设施编写OpenTofu模块及测试。模块存放于目录,测试文件存放于目录。
infrastructure/modules/infrastructure/modules/<name>/tests/Quick Reference
快速参考
bash
undefinedbash
undefinedRun tests for a module
运行模块测试
task tg:test-<module> # e.g., task tg:test-config
task tg:test-<module> # 示例:task tg:test-config
Format all HCL
格式化所有HCL文件
task tg:fmt
task tg:fmt
Version pinned in .opentofu-version (currently 1.11.2)
版本固定在.opentofu-version文件中(当前为1.11.2)
undefinedundefinedModule 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每个模块必须包含以下内容:
infrastructure/modules/<name>/
├── variables.tf # 带描述和验证规则的输入定义
├── main.tf # 核心资源与本地变量
├── outputs.tf # 输出定义
├── versions.tf # 提供商与OpenTofu版本约束
└── tests/ # 测试目录
└── *.tftest.hclTest File Structure
测试文件结构
Use extension. Define top-level for defaults inherited by all blocks.
.tftest.hclvariablesrunhcl
undefined使用扩展名。在顶层定义,为所有块设置默认继承值。
.tftest.hclvariablesrunhcl
undefinedTop-level variables set defaults for ALL run blocks
顶层变量为所有run块设置默认值
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"
}
}
undefinedvariables {
name = "test-cluster"
features = ["gateway-api", "longhorn"]
networking = {
id = 1
internal_tld = "internal.test.local"
# ... 其他必填字段
}
默认机器配置 - 除非被覆盖否则会被继承
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 # 使用plan模式 - 不会创建真实资源
variables {
features = ["prometheus"] # 仅覆盖需要修改的内容
}
assert {
condition = output.some_value == "expected"
error_message = "描述性失败信息"
}
}
undefinedKey Patterns
核心模式
Use command = plan
command = plan使用command = plan
command = planAlways use plan mode for tests. This validates configuration without creating resources.
测试始终使用plan模式。这样可以在不创建资源的情况下验证配置。
Variable Inheritance
变量继承
Only include variables in blocks when they differ from defaults. Minimizes duplication.
runhcl
undefined仅在块中包含与默认值不同的变量,减少重复代码。
runhcl
undefinedCORRECT: Override only what changes
正确写法:仅覆盖需要修改的内容
run "feature_enabled" {
command = plan
variables {
features = ["prometheus"]
}
assert { ... }
}
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
}
}
undefinedrun "feature_enabled" {
command = plan
variables {
name = "test-cluster" # 不必要 - 已继承
features = ["prometheus"]
machines = { ... } # 不必要 - 已继承
}
}
undefinedAssert Against Outputs
针对输出进行断言
Reference module outputs in assertions, not internal resources.
hcl
assert {
condition = length(output.machines) == 2
error_message = "Expected 2 machines"
}
assert {
condition = output.talos.kubernetes_version == "1.32.0"
error_message = "Version mismatch"
}在断言中引用模块输出,而非内部资源。
hcl
assert {
condition = length(output.machines) == 2
error_message = "预期2台机器"
}
assert {
condition = output.talos.kubernetes_version == "1.32.0"
error_message = "版本不匹配"
}Test Feature Flags
测试功能开关
Test both enabled and disabled states:
hcl
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"
}
}测试功能启用和禁用两种状态:
hcl
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 = "启用功能时应添加扩展"
}
}
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 = "未启用功能时不应存在扩展"
}
}Test Validations
测试验证规则
Use to verify variable validation rules:
expect_failureshcl
run "invalid_version_rejected" {
command = plan
variables {
versions = {
talos = "1.9.0" # Missing v prefix - should fail
# ...
}
}
expect_failures = [var.versions]
}使用验证变量验证规则:
expect_failureshcl
run "invalid_version_rejected" {
command = plan
variables {
versions = {
talos = "1.9.0" # 缺少v前缀 - 应验证失败
# ...
}
}
expect_failures = [var.versions]
}Common Assertions
常见断言示例
hcl
undefinedhcl
undefinedCheck length
检查长度
condition = length(output.items) == 3
condition = length(output.items) == 3
Check key exists
检查键是否存在
condition = contains(keys(output.map), "expected_key")
condition = contains(keys(output.map), "expected_key")
Check value in list
检查值是否在列表中
condition = contains(output.list, "expected_value")
condition = contains(output.list, "expected_value")
Check string contains
检查字符串是否包含子串
condition = strcontains(output.config, "expected_substring")
condition = strcontains(output.config, "expected_substring")
Check all items match
检查所有项是否匹配
condition = alltrue([for item in output.list : item.enabled == true])
condition = alltrue([for item in output.list : item.enabled == true])
Check any item matches
检查是否存在匹配项
condition = anytrue([for item in output.list : item.name == "target"])
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"
])
undefinedcondition = anytrue([
for label in output.machines["node1"].labels :
label.key == "expected-label" && label.value == "expected-value"
])
undefinedTest Organization
测试组织方式
Organize tests by concern:
- - Basic structure and output validation
plan.tftest.hcl - - Input validation rules
validation.tftest.hcl - - Feature flag behavior
feature_<name>.tftest.hcl - - Boundary conditions
edge_cases.tftest.hcl
按关注点组织测试:
- - 基础结构与输出验证
plan.tftest.hcl - - 输入验证规则
validation.tftest.hcl - - 功能开关行为
feature_<name>.tftest.hcl - - 边界条件
edge_cases.tftest.hcl
Detailed Reference
详细参考
For OpenTofu testing syntax, mock providers, and advanced patterns, see:
references/opentofu-testing.md
关于OpenTofu测试语法、模拟提供商及高级模式,请参考:
references/opentofu-testing.md