首页 > 解决方案 > Terraform for_each 如果对象中存在值

问题描述

我想从 .tfvars 文件动态创建一些子网和路由表,然后将每个子网链接到关联的路由表(如果指定)。

这是我的 .tfvars 文件:

vnet_spoke_object                      = {
    specialsubnets                     = {
        Subnet_1                       = {
            name                       = "test1"
            cidr                       = ["10.0.0.0/28"]
            route                      = "route1"
        }
        Subnet_2                       = {
            name                       = "test2"
            cidr                       = ["10.0.0.16/28"]
            route                      = "route2"
        }
        Subnet_3                       = {
            name                       = "test3"
            cidr                       = ["10.0.0.32/28"]
        }
    }
}

route_table                            = {
    route1                             = {
        name                           = "route1"
        disable_bgp_route_propagation  = true
        route_entries                  = {
            re1                        = {
                name                   = "rt-rfc-10-28"
                prefix                 = "10.0.0.0/28"
                next_hop_type          = "VirtualAppliance"
                next_hop_in_ip_address = "10.0.0.10"
            }
        }
    }
    route2                             = {
        name                           = "route2"
        disable_bgp_route_propagation  = true
        route_entries                  = {
            re1                        = {
                name                   = "rt-rfc-10-28"
                prefix                 = "10.0.0.16/28"
                next_hop_type          = "VirtualAppliance"
                next_hop_in_ip_address = "10.0.0.10"
            }
        }
    }
}

...这是我的构建脚本:

provider "azurerm" {
    version                        = "2.18.0"
    features{}
}

variable "ARM_LOCATION" {
    default                        = "uksouth"
}

variable "ARM_SUBSCRIPTION_ID" {
    default                        = "asdf-b31e023c78b8"
}

variable "vnet_spoke_object" {}
variable "route_table" {}

module "names" {
    source                         = "./nbs-azure-naming-standard"
    env                            = "dev"
    location                       = var.ARM_LOCATION
    subId                          = var.ARM_SUBSCRIPTION_ID
}

resource "azurerm_resource_group" "test" {
    name                           = "${module.names.standard["resource-group"]}-vnet"
    location                       = var.ARM_LOCATION
}

resource "azurerm_virtual_network" "test" {
    name                           = "${module.names.standard["virtual-network"]}-test"
    location                       = var.ARM_LOCATION
    resource_group_name            = azurerm_resource_group.test.name
    address_space                  = ["10.0.0.0/16"]
}

resource "azurerm_subnet" "test" {
    for_each                       = var.vnet_spoke_object.specialsubnets
    name                           = "${module.names.standard["subnet"]}-${each.value.name}"
    resource_group_name            = azurerm_resource_group.test.name
    virtual_network_name           = azurerm_virtual_network.test.name
    address_prefixes               = each.value.cidr
}

resource "azurerm_route_table" "test" {
    for_each                       = var.route_table
    name                           = "${module.names.standard["route-table"]}-${each.value.name}"
    location                       = var.ARM_LOCATION
    resource_group_name            = azurerm_resource_group.test.name
    disable_bgp_route_propagation  = each.value.disable_bgp_route_propagation
    dynamic "route" {
        for_each                   = each.value.route_entries
        content {
            name                   = route.value.name
            address_prefix         = route.value.prefix
            next_hop_type          = route.value.next_hop_type
            next_hop_in_ip_address = contains(keys(route.value), "next_hop_in_ip_address") ? route.value.next_hop_in_ip_address: null
        }
    }
}

该部分在创建 vnet/subnet/route 资源时工作正常,但我面临的问题是将每个子网动态链接到 .tfvars 中列出的路由表。并非所有子网都有与之关联的路由表,因此它只需要在列出键/值时运行route

resource "azurerm_subnet_route_table_association" "test" {
    for_each                       = {
        for key, value in var.vnet_spoke_object.specialsubnets:
            key => value
            if value.route != null
    }

    lifecycle {
        ignore_changes             = [
            subnet_id
        ]
    }
    subnet_id                      = azurerm_subnet.test[each.key].id
    route_table_id                 = azurerm_route_table.test[each.key].id
}

我在上面的代码中遇到的错误是:

Error: Unsupported attribute

  on main.tf line 65, in resource "azurerm_subnet_route_table_association" "test":
  65:             if value.route != null

This object does not have an attribute named "route".

我尝试了各种方法都没有成功,我在这里不知所措,并希望得到任何可能的指导。

标签: terraformterraform0.12+

解决方案


根据您的情况,我猜测输入中的vnet_spoke_object如下所示:

vnet_spoke_object = {
  specialsubnets = {
    subnetA = {
      cidr  = "..."
    }
    subnetB = {
      cidr  = "..."
      route = "..."
    }
  }
}

这样做的问题是缺少的路由条目不会解析为null,它会导致恐慌或崩溃。您需要像这样编写输入(使用显式null s):

vnet_spoke_object = {
  specialsubnets = {
    subnetA = {
      cidr  = "..."
      route = null
    }
    subnetB = {
      cidr  = "..."
      route = "..."
    }
  }
}

或按名称查找 路线并在您的地图生成器表达式中提供null默认值,如下所示:

for_each                       = {
    for key, value in var.vnet_spoke_object.specialsubnets:
        key => value
        if lookup(value, "route", null) != null
}

推荐阅读