【问题标题】:Concatenate two lists in Terraform 0.12 - concat()在 Terraform 0.12 中连接两个列表 - concat()
【发布时间】:2019-10-22 22:06:37
【问题描述】:

我想在 Terraform 0.12 中连接两个数组。在我的示例中,有公共子网和私有子网。我想将它们都分配给相同的网络访问列表。以下代码缩短:

data "aws_subnet_ids" "private" {
    vpc_id = aws_vpc.main.id
    tags = {
        subnet-type  = "private"
    }
}

data "aws_subnet_ids" "public" {
    vpc_id = aws_vpc.main.id
    tags = {
        subnet-type  = "public"
    }
}


resource "aws_network_acl" "networks" {
    vpc_id = aws_vpc.main.id

    subnet_ids = concat(data.aws_subnet_ids.private.ids, data.aws_subnet_ids.public.ids)
    [...]
}

如果我使用以下输出:

output "private_subnets" {
  value = data.aws_subnet_ids.private.ids
}

output "public_subnets" {
  value = data.aws_subnet_ids.public.ids
}

生成以下输出:

private_subnets = [
  "subnet-243zr427rhhfjseb9",
  "subnet-we789rh2438fchb6e",
  "subnet-092rz7g82fhhkui74",
]
public_subnets = [
  "subnet-12230qegvg764e9d",
  "subnet-123465svgvgf0d7e",
]

所以一切都应该工作。但是报错如下:

iptizer@machine:~/src/infra$ terraform12 apply
[...]

Error: Invalid function argument

  on nacls.tf line 19, in resource "aws_network_acl" "networks":
  19:     subnet_ids = concat(data.aws_subnet_ids.private.ids, data.aws_subnet_ids.public.ids)
    |----------------
    | data.aws_subnet_ids.private.ids is set of string with 3 elements

Invalid value for "seqs" parameter: all arguments must be lists or tuples; got
set of string.

Bug.. 还是我错过了什么?

【问题讨论】:

标签: terraform


【解决方案1】:

与 0.11 相比,Terraform 0.12 对列表值和集合值进行了更严格的区分,并包括一些类似这样的额外检查。

在这种特殊情况下,concat 以这种方式失败,因为连接要求所有元素具有明确定义的顺序,以便结果也可以具有明确定义的顺序。集合是没有排序的,所以这个检查是为了提醒您在转换为列表时明确选择合适的排序,或者根本不转换为列表。

在这种特殊情况下,排序似乎并不特别重要,因此sort 实现的词汇排序可能就足够了:

  subnet_ids = concat(
    sort(data.aws_subnet_ids.private.ids),
    sort(data.aws_subnet_ids.public.ids),
  )

(因为从字符串列表集到字符串列表的转换也强加了词法排序,这在功能上等同于字符串集的tolist。我通常更喜欢sort,因为它是未来读者的线索,即结果将按词法顺序排列。)

另一种选择是在集合世界中使用setunion 代替:

  subnet_ids = setunion(
    data.aws_subnet_ids.private.ids,
    data.aws_subnet_ids.public.ids,
  )

由于这两个列表之间不应有重复项,因此您在此处使用哪种方法并不重要,但为了完整起见,我会指出,如果这两个列表都包含相同的子网 ID setunion操作会对它们进行重复数据删除,因为每个唯一值在集合中只能出现零次或一次。


在我写这篇文章的时候,count 仍然是为集合中的每个项目创建一个资源实例的主要方式,通常需要转换为列表最终,以便单个实例可以有一个需要的顺序。 Once for_each is implemented,在这种情况下使用集合而不是列表会有优势:

resource "aws_instance" "per_subnet_example" {
  # resource-level for_each is not implemented at the time of writing,
  # but planned for a future release.
  for_each = setunion(
    data.aws_subnet_ids.private.ids,
    data.aws_subnet_ids.public.ids,
  )

  # ...
}

当在集合上使用 for_each 而不是 count 时,Terraform 将通过集合中的值而不是连续索引来识别每个实例,因此该资源中的一个实例可能具有地址 aws_instance.per_subnet_example["subnet-abc123"],并且意味着当从该集合中添加和删除元素时,Terraform 可以只创建/销毁相应的单个实例,而不是在有序序列更改后重新创建所有内容。

Terraform 提供者在有意义的地方使用这样的集合,以便使 for_each 模式在它到达后更易于使用,但不幸的是,这意味着我们需要同时编写一些额外的显式类型转换以便明确表示我们正在以类似序列的方式而不是类似集合的方式处理这些值。

【讨论】:

    【解决方案2】:

    我只是简化了解决方案。当输出子网 id 使用 aws_subnet.whatever[*].id

    1.在 VPC 模块的 output.tf 中使用以下语法

    output "private_subnet_ids" {
      value = aws_subnet.private[*].id
    }
    
    output "public_subnet_ids" {
      value = aws_subnet.public[*].id
    }
    

    2。在子模块定义中使用

    module "acl" {
      source                = "../.../anything"
      private_subnet_ids    = data.terraform_remote_state.vpc.outputs.private_subnet_ids
      public_subnet_ids    = data.terraform_remote_state.vpc.outputs.public_subnet_ids
      ....
      ... other options
    }
    

    module "acl" {
      source                = "../.../anything"
      private_subnet_ids     = module.vpc.outputs.private_subnet_ids
      public_subnet_ids    = module.vpc.outputs.public_subnet_ids
      ....
      ... other options
    }
    

    3.然后,在子模块中只需使用以下语法

    资源“aws_network_acl”“网络”{ vpc_id = aws_vpc.main.id

    subnet_ids = concat(var.public_subnet_ids, var.private_subnet_ids)
    [...]
    

    }

    【讨论】:

      【解决方案3】:

      在 terraform-providers/terraform-provider-aws 问题中已经报告了类似的问题。

      https://github.com/terraform-providers/terraform-provider-aws/issues/7522

      并且也提到了相同的解决方法。对于您的方案,您可以像下面这样解决方法

      解决 #1

      locals {                                                            
        private_subnet_ids_string = join(",", data.aws_subnet_ids.private.ids)
        private_subnet_ids_list = split(",", local.private_subnet_ids_string)
        public_subnet_ids_string = join(",", data.aws_subnet_ids.public.ids)
        public_subnet_ids_list = split(",", local.public_subnet_ids_string)             
      }
      
      resource "aws_network_acl" "networks" {
          vpc_id = aws_vpc.main.id
      
          subnet_ids = concat(local.private_subnet_ids_list, local.public_subnet_ids_list)
          [...]
      }
      

      解决 #2

      resource "aws_network_acl" "networks" {
          vpc_id = aws_vpc.main.id
      
          subnet_ids = concat(tolist(data.aws_subnet_ids.private.ids), tolist(data.aws_subnet_ids.public.ids))
          [...]
      }
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-05
        • 2019-11-28
        • 2014-11-18
        • 1970-01-01
        • 2020-06-23
        • 2021-01-23
        • 2015-12-16
        • 2020-08-11
        相关资源
        最近更新 更多