如何将YAML列表转换为Terraform动态FortiOS防火墙地址资源
问题描述
我不擅长数据结构与算法,已经编写了一份包含客户端名称、服务、CIDR列表的YAML配置,这个结构更适配不同名称的客户端列表需求。希望基于该YAML在Terraform中创建类似fortios_firewall_address.address["bank_of_bravos-127.0.0.1"]的动态FortiOS防火墙地址资源。尝试通过本地变量将YAML转为Map结构,但在嵌套列表迭代、配置resource的for_each时,出现属性不存在、字符串插值错误等问题,寻求正确实现方法。
YAML配置
--- clients: - name: "bank_of_braavos" services: - "HTTPS" - "DNS" cidr: - "127.0.0.1/32" - "127.0.0.2/32" - "127.0.0.3/32" - name: "tatooine" services: - "HTTPS" - "ICMP" cidr: - "958.xx.21.1r/aar" - "958.xx.21.1g/fwr"
尝试的本地变量配置
locals { clients_config = yamldecode(file("clients.yml")) clients = { for client in local.clients_config.clients : client.name => { cidr = client.cidr services = client.services } } }
尝试的资源配置(报错)
resource "fortios_firewall_address" "clients" { for_each = { for client in local.clients : "${client.name}-${client.cidr}" => client } allow_routing = "disable" name = "Client_${each.client.name}_${each.client.cidr}" subnet = "${each.value.cidr}" type = "ipmask" visibility = "enable" }
报错信息
│ Error: Unsupported attribute │ │ on policy.tf line 15, in resource "fortios_firewall_address" "clients": │ 15: for_each = { for client in local.clients : "${client.name}-${client.cidr}" => client } │ │ This object does not have an attribute named "name". ╵ ╷ │ Error: Unsupported attribute │ │ on policy.tf line 15, in resource "fortios_firewall_address" "clients": │ 15: for_each = { for client in local.clients : "${client.name}-${client.cidr}" => client } │ │ This object does not have an attribute named "name". ╵ ╷ │ Error: Invalid template interpolation value │ │ on policy.tf line 15, in resource "fortios_firewall_address" "clients": │ 15: for_each = { for client in local.clients : "${client.name}-${client.cidr}" => client } │ ├──────────────── │ │ client.cidr is tuple with 3 elements │ │ Cannot include the given value in a string template: string required.
解决方法
核心问题是未正确展开嵌套的CIDR列表,同时遍历local.clients时混淆了Map键值的结构。以下是修正后的完整实现:
1. 修正本地变量(生成扁平化的CIDR条目)
需要将每个客户端的CIDR列表展开,生成「客户端名称+单个CIDR」的独立条目,确保每个防火墙地址资源有唯一标识:
locals { clients_config = yamldecode(file("clients.yml")) # 扁平化数据:把每个客户端的CIDR拆分为独立键值对 client_cidrs = merge([ for client in local.clients_config.clients : { for cidr in client.cidr : "${client.name}-${cidr}" => { client_name = client.name cidr = cidr services = client.services } } ]...) }
通过嵌套for表达式+merge函数,将原数据中的多CIDR条目拆分为单个条目,键格式与你期望的bank_of_bravos-127.0.0.1一致。
2. 修正防火墙地址资源配置
直接使用扁平化后的local.client_cidrs作为for_each数据源:
resource "fortios_firewall_address" "clients" { for_each = local.client_cidrs allow_routing = "disable" name = "Client_${each.value.client_name}_${replace(each.value.cidr, "/", "_")}" subnet = each.value.cidr type = "ipmask" visibility = "enable" }
for_each = local.client_cidrs:直接使用扁平化后的Map,每个键都是唯一的「客户端名称-CIDR」组合name字段用replace将CIDR中的/替换为_,避免生成非法资源名称(多数系统不允许名称含/)subnet直接引用each.value.cidr,确保是单个字符串而非列表
关键错误分析
- 属性不存在错误:你之前的
local.clients是以客户端名称为键的Map,遍历它时client是Map的值(仅包含cidr和services),没有name属性,因此报错。 - 字符串插值错误:
client.cidr是列表类型,无法直接插入字符串,必须先展开为单个CIDR条目。
内容的提问来源于stack exchange,提问作者Clear Sky
相关产品推荐
相关产品推荐

