在Terraform 0.12中并置两个列表-concat()

时间:2019-06-07 11:39:40

标签: terraform

我想在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 ..或我想念什么?

2 个答案:

答案 0 :(得分:2)

与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,{{1 }}操作会对其进行重复数据删除,因为每个唯一值在一组中只能出现零或一次。


在撰写本文时,setunion仍然是在集合中为每个项目创建一个资源实例的主要方法,通常需要最终转换为列表,因此,各个实例可以具有所需的顺序。 Once for_each is implemented,在这种情况下使用集合而不是列表将是一个优势:

count

在集合上使用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时,Terraform将通过集合中的值而不是连续索引来标识每个实例,因此该资源中的一个实例可能具有地址{{ 1}},这意味着从该集合添加元素或从其中删除元素时,Terraform只能创建/销毁相应的单个实例,而不必在有序序列更改后重新创建所有内容。

Terraform提供商在有意义的地方使用了这样的集合,以使count模式一旦到达就更易于使用,但是不幸的是,这意味着我们需要同时编写一些额外的显式类型转换为了明确我们正在以类似于序列的方式而不是基于集合的方式来使用这些值。

答案 1 :(得分:0)

在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))
    [...]
}