如何使用flatten函数在Terraform 中迭代嵌套map

发布于:2025-04-20 ⋅ 阅读:(57) ⋅ 点赞:(0)

简介

flatten 接受一个列表,并用列表内容的扁平序列替换列表中的任何元素。

> flatten([["a", "b"], [], ["c"]])
["a", "b", "c"]
> flatten([[["a", "b"], []], ["c"]])
["a", "b", "c"]

间接嵌套列表(例如map中的列表)不会被展平。 

扁平化 for_each 的嵌套结构


资源 for_each 和动态块语言功能都需要一个集合值,该值每次重复都包含一个元素。

有时,您的输入数据结构本身并不适合用于 for_each 参数,而 flatten 函数在将嵌套数据结构简化为扁平结构时非常有用。

案例1

声明变量

例如,考虑一个声明如下变量的模块:

variable "networks" {
  type = map(object({
    cidr_block = string
    subnets    = map(object({ cidr_block = string }))
  }))
  default = {
    "private" = {
      cidr_block = "10.1.0.0/16"
      subnets = {
        "db1" = {
          cidr_block = "10.1.0.1/16"
        }
        "db2" = {
          cidr_block = "10.1.0.2/16"
        }
      }
    },
    "public" = {
      cidr_block = "10.1.1.0/16"
      subnets = {
        "webserver" = {
          cidr_block = "10.1.1.1/16"
        }
        "email_server" = {
          cidr_block = "10.1.1.2/16"
        }
      }
    }
    "dmz" = {
      cidr_block = "10.1.2.0/16"
      subnets = {
        "firewall" = {
          cidr_block = "10.1.2.1/16"
        }
      }
    }
  }
}

 以上是对自然形成树的对象(例如顶级网络及其子网)进行建模的合理方法。顶级网络的重复可以直接使用此变量,因为它已经是结果实例与映射元素一一匹配的形式:

resource "aws_vpc" "example" {
  for_each = var.networks

  cidr_block = each.value.cidr_block
}

展平network_subnets

但是,为了用单个资源块声明所有子网,我们必须首先展平结构以生成一个集合,其中每个顶级元素代表一个子网: 

locals {
  # flatten ensures that this local value is a flat list of objects, rather
  # than a list of lists of objects.
  network_subnets = flatten([
    for network_key, network in var.networks : [
      for subnet_key, subnet in network.subnets : {
        network_key = network_key
        subnet_key  = subnet_key
        network_id  = aws_vpc.example[network_key].id
        cidr_block  = subnet.cidr_block
      }
    ]
  ])
}

resource "aws_subnet" "example" {
  # local.network_subnets is a list, so we must now project it into a map
  # where each key is unique. We'll combine the network and subnet keys to
  # produce a single unique key per instance.
  for_each = tomap({
    for subnet in local.network_subnets : "${subnet.network_key}.${subnet.subnet_key}" => subnet
  })

  vpc_id            = each.value.network_id
  availability_zone = each.value.subnet_key
  cidr_block        = each.value.cidr_block
}

 上述结果是每个子网对象有一个子网实例,同时保留子网与其包含的网络之间的关联。

运行terraform plan

  # aws_vpc.example["public"] will be created
  + resource "aws_vpc" "example" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.2.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_dns_hostnames                 = (known after apply)
      + enable_dns_support                   = true
      + enable_network_address_usage_metrics = (known after apply)
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags_all                             = (known after apply)
    }

Plan: 8 to add, 0 to change, 0 to destroy.

terraform将创建5个子网和3个vpc。

  • private vpc 和子网db1 、db2 
  • public vpc 和子网 webserver 、email_server
  • dmz vpc和firewall 子网

 案例2

我们将通过一个将策略附加到角色的简单示例,探索如何在 Terraform 中迭代嵌套映射。

设置嵌套映射


首先,我们需要创建一个变量来保存嵌套映射:

variable "role_map" {
  default = {
    "role_1" = {
      "name" : "Role 1"
      "policies" : ["policy_arn_1", "policy_arn_2", "policy_arn_3"],
    },
    "role_2" = {
      "name" : "Role 2"
      "policies" : ["policy_arn_2", "policy_arn_4", "policy_arn_6"],
    }
  }
}

如您所见,每个角色都包含许多策略,这在循环遍历映射时会造成麻烦。

嵌套映射


为了将其转换为可迭代的格式,我们需要展平此映射:

locals {
  role_policies = flatten([
    for role_key, role in var.role_map : [
      for p, policy in role.policies : {
        role_key  = role_key
        role_name = role.name
        policy_arn  = policy
      }
    ]
  ])
}

测试Flattened map


我们可以使用 terraform console 命令来确认其是否按预期运行。

在控制台中,运行 local.role_policies。您现在应该会看到以下输出:

ninjamac@ip-192-168-1-2 nested_for_each % terraform console
> local.role_policies
[
  {
    "policy_arn" = "policy_arn_1"
    "role_key" = "role_1"
    "role_name" = "Role 1"
  },
  {
    "policy_arn" = "policy_arn_2"
    "role_key" = "role_1"
    "role_name" = "Role 1"
  },
  {
    "policy_arn" = "policy_arn_3"
    "role_key" = "role_1"
    "role_name" = "Role 1"
  },
  {
    "policy_arn" = "policy_arn_2"
    "role_key" = "role_2"
    "role_name" = "Role 2"
  },
  {
    "policy_arn" = "policy_arn_4"
    "role_key" = "role_2"
    "role_name" = "Role 2"
  },
  {
    "policy_arn" = "policy_arn_6"
    "role_key" = "role_2"
    "role_name" = "Role 2"
  },
]

现在,这是理想的格式。每个策略都已展平,但仍引用其应附加到的原始角色。

迭代展平后的 Map


现在,我们可以循环遍历展平后的 local.role_policies 了:

resource "example_terraform_resource_attach_policy" "example" {
  for_each = { for p, policy in local.role_policies : p => policy }

  policy_arn = each.value.policy_arn
  role_key = each.value.role_key
}

这将创建具有正确 policy_arn 的所有 6 个策略,并为其分配正确的 role_key。 

案例3:

模块:

locals {
  nestedlist = flatten([
    for k, v in var.route : [
      for n, s in v : [
        {
          key = k,
          name = n,
          svc_url = s
        }
      ]
    ]
  ])
}
resource "aws_appmesh_gateway_route" "gateway_route" {
  for_each             = { for o in local.nestedlist : o.name => o }
  
  name                 = trimprefix(each.value.name, "/")
  mesh_name            = var.mesh_name
  virtual_gateway_name = var.virtual_gateway_name
  spec {
    http_route {
      action {
        target {
          virtual_service {
            virtual_service_name = each.value.svc_url
          }
        }
      }
      match {
        prefix = each.value.name
      }
    }
  }
}

 变量:

virtual_gateway_name = "backend"
mesh_name = "development"
route = {
  "region" = {
    "/north" = "north.dev.svc.cluster.local"
    "/south" = "south.dev.svc.cluster.local"
    "/east"  = "east.dev.svc.cluster.local"
    "/west"  = "west.dev.svc.cluster.local"
  }
  "direction" = {
    "/up" = "up.dev.svc.cluster.local"
    "/down" = "down.dev.svc.cluster.local"
    "/right" = "right.dev.svc.cluster.local"
    "/left" = "left.dev.svc.cluster.local"
  }
}

该示例使用 flatten 函数以递归方式展平列表和嵌套列表,以获取前缀名称和服务 URL。这样,Terraform 模块现已重构,可以创建多组虚拟网关路由。将来,我可以通过将其添加到路由列表中来添加更多虚拟网关路由。

如输出所示,将创建八个资源

输出:


# aws_appmesh_gateway_route.gateway_route["/west"] will be created
  + resource "aws_appmesh_gateway_route" "gateway_route" {
      + arn                  = (known after apply)
      + created_date         = (known after apply)
      + id                   = (known after apply)
      + last_updated_date    = (known after apply)
      + mesh_name            = "development"
      + mesh_owner           = (known after apply)
      + name                 = "west"
      + resource_owner       = (known after apply)
      + tags_all             = (known after apply)
      + virtual_gateway_name = "backend"
+ spec {
+ http_route {
              + action {
                  + target {
                      + virtual_service {
                          + virtual_service_name = "west.dev.svc.cluster.local"
                        }
                    }
                }
+ match {
                  + prefix = "/west"
                }
            }
        }
    }
Plan: 8 to add, 0 to change, 0 to destroy.

总结

Terraform 中的 flatten 函数用于将嵌套列表合并为单个一维列表。这对于简化复杂的数据结构以及更轻松地处理资源定义中的项目集合特别有用。通过展平列表,您可以确保数据采用更易于管理的格式,以便进行迭代等操作。