我想在 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.
错误..或者我错过了什么?
Terraform 0.12 比 0.11 对列表值和设置值进行了更强的区分,并包括一些像这样的额外检查。
在这种特殊情况下,
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
仍然是为集合中的每个项目创建一个资源实例的主要方式,通常需要转换为列表最终,以便各个实例可以具有所需的顺序。 一旦实现for_each
,在这种情况下使用集合而不是列表将会有一个优势:
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
模式在到达后更易于使用,但不幸的是,这意味着我们需要同时编写一些额外的显式类型转换,以便明确我们正在以类似序列的方式而不是类似集合的方式处理这些值。
在 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))
[...]
}
我只是简化了解决方案。当输出子网 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)
[...]
}