简体   繁体   English

Terraform - 如何在对象列表上使用 for_each 循环来创建资源

[英]Terraform - how to use for_each loop on a list of objects to create resources

I have an object containing the list of subnets I want to create.我有一个包含要创建的子网列表的对象。

variable "subnet-map" {
  default = {
    ec2 = [
      {
        cidr_block        = "10.0.1.0/24"
        availability_zone = "eu-west-1a"
      }
    ],
    lambda = [
      {
        cidr_block        = "10.0.5.0/24"
        availability_zone = "eu-west-1a"
      },
      {
        cidr_block        = "10.0.6.0/24"
        availability_zone = "eu-west-1b"
      },
      {
        cidr_block        = "10.0.7.0/24"
        availability_zone = "eu-west-1c"
      }
    ],
    secrets = [
      {
        cidr_block        = "10.0.8.0/24"
        availability_zone = "eu-west-1a"
      },
      {
        cidr_block        = "10.0.9.0/24"
        availability_zone = "eu-west-1b"
      },
      {
        cidr_block        = "10.0.10.0/24"
        availability_zone = "eu-west-1c"
      }
    ],
    rds = [
      {
        cidr_block        = "10.0.11.0/24"
        availability_zone = "eu-west-1a"
      },
      {
        cidr_block        = "10.0.12.0/24"
        availability_zone = "eu-west-1b"
      },
      {
        cidr_block        = "10.0.13.0/24"
        availability_zone = "eu-west-1c"
      }
    ]
  }
}

Earlier I was using the count loop construct.早些时候我使用了计数循环结构。 So I used to flatten the above structure into a list of objects所以我曾经把上面的结构扁平化成一个对象列表

locals {
  subnets = flatten([
    for resource in keys(var.subnet-map) : [
      for subnet in var.subnet-map[resource] : {
        resource          = resource
        cidr_block        = subnet.cidr_block
        availability_zone = subnet.availability_zone
      }
    ]
  ])
}

And then I would create the resources by doing然后我会通过做来创建资源

resource "aws_subnet" "aws-subnets" {
  count             = length(local.subnets)
  vpc_id            = aws_vpc.aws-vpc.id
  cidr_block        = local.subnets[count.index].cidr_block
  availability_zone = local.subnets[count.index].availability_zone

  tags = {
    Name = "subnet-${local.subnets[count.index].resource}-${local.subnets[count.index].availability_zone}"
  }
}

Now I want to use the for_each loop.现在我想使用 for_each 循环。 But I cannot figure out how to do it.但我不知道该怎么做。 This is what I've done so far.这是我到目前为止所做的。

resource "aws_subnet" "subnets-dev" {
  for_each          = var.subnet-map
  vpc_id            = aws_vpc.vpc-dev.id
  cidr_block        = each.value.cidr_block
  availability_zone = each.value.availability_zone

  tags = {
    Name        = "subnet-dev-${each.value.resource}-${each.value.availability_zone}"
    environment = "dev"
  }
}

But it keeps giving an error saying但它不断给出一个错误说

Error: Unsupported attribute

  on vpc/main.tf line 93, in resource "aws_subnet" "subnets-dev":
  93:     Name        = "subnet-dev-${each.value.resource}-${each.value.availability_zone}"
    |----------------
    | each.value is tuple with 3 elements

This value does not have any attributes.

How could I fix this?我怎么能解决这个问题?

I'm not sure I fully follow all of what you tried here because your initial snippet of var.subnet-map shows it being a map of maps of lists of objects, but later on when you used for_each = var.subnet-map it seems to have treated it as a map of lists instead.我不确定我是否完全遵循您在此处尝试的所有内容,因为var.subnet-map初始片段显示它是对象列表映射的映射,但是稍后当您使用for_each = var.subnet-map时似乎已将其视为列表映射。 Did you remove that extra level of maps (the "default" key) before trying for_each here?在尝试for_each之前,您是否删除了额外的地图级别(“默认”键)?

Working with your original definition of variable "subnet-map" , your first step with for_each will be similar to what you did with count : you need to flatten the structure, this time into a map of objects rather than a list of objects.使用您对variable "subnet-map"原始定义,您对for_each第一步将类似于您对count所做的:您需要将结构展平,这次是一个对象映射而不是对象列表。 The easiest way to get there is to derive a map from your existing flattened list:到达那里的最简单方法是从现有的扁平化列表中获取地图:

locals {
  subnets = flatten([
    for resource in keys(var.subnet-map) : [
      for subnet in var.subnet-map[resource] : {
        resource          = resource
        cidr_block        = subnet.cidr_block
        availability_zone = subnet.availability_zone
      }
    ]
  ])

  subnets_map = {
    for s in local.subnets: "${s.resource}:${s.availability_zone}" => s
  }
}

Here I assumed that your "resource" string and your availability zone together are a suitable unique identifier for a subnet.在这里,我假设您的“资源”字符串和可用区一起是一个合适的子网唯一标识符。 If not, you can adjust the "${s.resource}:${s.availability_zone}" part to whatever unique key you want to use for these.如果没有,您可以将"${s.resource}:${s.availability_zone}"部分调整为您想要用于这些的任何唯一键。

Now you can use the flattened map as the for_each map:现在你可以使用扁平化的地图作为for_each地图:

resource "aws_subnet" "subnets-dev" {
  for_each          = local.subnets_map
  vpc_id            = aws_vpc.vpc-dev.id
  cidr_block        = each.value.cidr_block
  availability_zone = each.value.availability_zone

  tags = {
    Name        = "subnet-dev-${each.value.resource}-${each.value.availability_zone}"
    environment = "dev"
  }
}

This will give you instances with addresses like aws_subnet.subnets-dev["ec2:eu-west-1a"] .这将为您提供地址如aws_subnet.subnets-dev["ec2:eu-west-1a"]实例。


Note that if you are migrating from count with existing subnets that you wish to retain, you'll need to also do a one-time migration step to tell Terraform which indexes from the existing state correspond to which keys in the new configuration.请注意,如果您要从count迁移到您希望保留的现有子网,您还需要执行一次性迁移步骤,以告诉 Terraform 现有状态中的哪些索引对应于新配置中的哪些键。

For example, if (and only if) index 0 was previously the one for ec2 in eu-west-1a , the migration command for that one would be:例如,如果(且仅当)索引0之前是eu-west-1a ec2索引,则该索引的迁移命令将是:

terraform state mv 'aws_subnet.subnets-dev[0]' 'aws_subnet.subnets-dev["ec2:eu-west-1a"]'

If you're not sure how they correlate, you can run terraform plan after adding for_each and look at the instances that Terraform is planning to destroy.如果您不确定它们之间的关系,您可以在添加for_each后运行terraform plan并查看 Terraform 计划销毁的实例。 If you work through each one of those in turn, taking the address Terraform currently knows along with the resource and availability zone names shown in the Name tag, you can migrate each of them to its new address so that Terraform will no longer think you're asking for it to destroy the numbered instances and replace them with named ones.如果您依次处理其中的每一个,将 Terraform 当前知道的地址与Name标签中显示的资源和可用区名称一起使用,您可以将它们中的每一个迁移到其新地址,以便 Terraform 不再认为您重新要求它销毁编号的实例并用命名的实例替换它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM