如何在 Terraform 资源循环中使用非唯一键创建唯一资源
How to Use a Non-unique Key in Terraform Resource Loop to Create Unique Resources
我想使用通用模块为域创建每种类型的 DNS 记录,这样就可以用类似的方式调用它:
module "example_com_dns" {
source = "[PATH_TO_MODULES]/modules/dns"
domain = "example.com"
a_records = {
"@" = [SOME IP]
"www" = [SOME IP]
"home" = [SOME IP]
}
txt_records = {
"@" = "txt-foobar1"
"@" = "txt-foobar2"
"mail._domainkey.self" = "foobar"
}
mx_entries = {
"10" = "mail.someprovider.com"
"20" = "mail2.someprovider.com"
}
cname_records {
"cname-foo" = "cname-bar
}
}
我有一些东西可以很好地用于 A、CNAME 和 MX 记录,但是 TXT 有一个边缘情况,我需要解决这个问题。我的模块具有用于每种记录类型的资源块,运行 通过循环。我就直接贴TXT了,不过都是一样的:
resource "digitalocean_record" "this_txt_record" {
for_each = var.txt_records
domain = var.domain
type = "TXT"
name = each.key
value = each.value
}
一切正常,除了,因为有 2 条记录的键为“@”,结果只创建了最后一条(在我的上面的例子,这是“txt-foobar2”):
...
# module.example_com.digitalocean_record.this_txt_record["@"] will be created
+ resource "digitalocean_record" "this_txt_record" {
+ domain = "example.com"
+ fqdn = (known after apply)
+ id = (known after apply)
+ name = "@"
+ ttl = (known after apply)
+ type = "TXT"
+ value = "txt-foobar2"
}
我希望它同时创建“txt-foobar1”和“txt-foobar2”,即使在地图中给定非唯一键也是如此。
也许这是错误的方法,我只需要找出一个聪明的循环来代替解析这个结构?:
txt_records = [
{ "@" = "foo" },
{ "@" = "bar"},
{ "mail._domainkey.self" = "foobar"}
]
如果是这样,我目前也失败了:)
无法通过 for_each
列表来创建资源,因为必须有一个唯一键,该键将成为 terraform 资源名称的一部分。列表索引不能成为可靠的键,因为如果您重新排序列表中的项目,您的 TF 计划将一团糟。
另一方面,根据定义,地图确实具有唯一键。
不过您可以从列表中生成地图!我发现了这个小技巧here。请注意,您还需要手动计算唯一映射键(下例中的 ${txt_record[0]}=${txt_record[1]}
)。
您的更新资源:
module "example_com_dns" {
...
txt_records = [
["@", "txt-foobar1"],
["@", "txt-foobar2"],
["mail._domainkey.self", "foobar"],
]
}
resource "digitalocean_record" "this_txt_record" {
for_each = {for txt_record in var.txt_records: "${txt_record[0]}=${txt_record[1]}" => txt_record}
domain = var.domain
type = "TXT"
name = each.value[0]
value = each.value[1]
}
或者如果您愿意,可以稍微详细一点:
module "example_com_dns" {
...
txt_records = [
{name: "@", value: "txt-foobar1"},
{name: "@", value: "txt-foobar2"},
{name: "mail._domainkey.self", value: "foobar"},
]
}
resource "digitalocean_record" "this_txt_record" {
for_each = {for txt_record in var.txt_records: "${txt_record.name}=${txt_record.value}" => txt_record}
domain = var.domain
type = "TXT"
name = each.value.name
value = each.value.value
}
已经给出的另一种方法是使用以下方法:
variable "txt_records" {
default = {
"@" = ["foo", "bar"],
"mail._domainkey.self" = ["foobar"]
}
}
然后您可以使用以下方法展平 txt_records
:
locals {
txt_records_flat = merge([
for key, values in var.txt_records:
{for value in values:
"${key}-${value}" => {"record_name" = key, "record_value" = value}
}
]...)
}
这导致 local.txt_records_flat
个:
{
"@-bar" = {
"record_name" = "@"
"record_value" = "bar"
}
"@-foo" = {
"record_name" = "@"
"record_value" = "foo"
}
"mail._domainkey.self-foobar" = {
"record_name" = "mail._domainkey.self"
"record_value" = "foobar"
}
}
那你就用它:
resource "digitalocean_record" "this_txt_record" {
for_each = local.txt_records_flat
domain = var.domain
type = "TXT"
name = each.value.record_name
value = each.value.record_value
}
我想使用通用模块为域创建每种类型的 DNS 记录,这样就可以用类似的方式调用它:
module "example_com_dns" {
source = "[PATH_TO_MODULES]/modules/dns"
domain = "example.com"
a_records = {
"@" = [SOME IP]
"www" = [SOME IP]
"home" = [SOME IP]
}
txt_records = {
"@" = "txt-foobar1"
"@" = "txt-foobar2"
"mail._domainkey.self" = "foobar"
}
mx_entries = {
"10" = "mail.someprovider.com"
"20" = "mail2.someprovider.com"
}
cname_records {
"cname-foo" = "cname-bar
}
}
我有一些东西可以很好地用于 A、CNAME 和 MX 记录,但是 TXT 有一个边缘情况,我需要解决这个问题。我的模块具有用于每种记录类型的资源块,运行 通过循环。我就直接贴TXT了,不过都是一样的:
resource "digitalocean_record" "this_txt_record" {
for_each = var.txt_records
domain = var.domain
type = "TXT"
name = each.key
value = each.value
}
一切正常,除了,因为有 2 条记录的键为“@”,结果只创建了最后一条(在我的上面的例子,这是“txt-foobar2”):
...
# module.example_com.digitalocean_record.this_txt_record["@"] will be created
+ resource "digitalocean_record" "this_txt_record" {
+ domain = "example.com"
+ fqdn = (known after apply)
+ id = (known after apply)
+ name = "@"
+ ttl = (known after apply)
+ type = "TXT"
+ value = "txt-foobar2"
}
我希望它同时创建“txt-foobar1”和“txt-foobar2”,即使在地图中给定非唯一键也是如此。
也许这是错误的方法,我只需要找出一个聪明的循环来代替解析这个结构?:
txt_records = [
{ "@" = "foo" },
{ "@" = "bar"},
{ "mail._domainkey.self" = "foobar"}
]
如果是这样,我目前也失败了:)
无法通过 for_each
列表来创建资源,因为必须有一个唯一键,该键将成为 terraform 资源名称的一部分。列表索引不能成为可靠的键,因为如果您重新排序列表中的项目,您的 TF 计划将一团糟。
另一方面,根据定义,地图确实具有唯一键。
不过您可以从列表中生成地图!我发现了这个小技巧here。请注意,您还需要手动计算唯一映射键(下例中的 ${txt_record[0]}=${txt_record[1]}
)。
您的更新资源:
module "example_com_dns" {
...
txt_records = [
["@", "txt-foobar1"],
["@", "txt-foobar2"],
["mail._domainkey.self", "foobar"],
]
}
resource "digitalocean_record" "this_txt_record" {
for_each = {for txt_record in var.txt_records: "${txt_record[0]}=${txt_record[1]}" => txt_record}
domain = var.domain
type = "TXT"
name = each.value[0]
value = each.value[1]
}
或者如果您愿意,可以稍微详细一点:
module "example_com_dns" {
...
txt_records = [
{name: "@", value: "txt-foobar1"},
{name: "@", value: "txt-foobar2"},
{name: "mail._domainkey.self", value: "foobar"},
]
}
resource "digitalocean_record" "this_txt_record" {
for_each = {for txt_record in var.txt_records: "${txt_record.name}=${txt_record.value}" => txt_record}
domain = var.domain
type = "TXT"
name = each.value.name
value = each.value.value
}
已经给出的另一种方法是使用以下方法:
variable "txt_records" {
default = {
"@" = ["foo", "bar"],
"mail._domainkey.self" = ["foobar"]
}
}
然后您可以使用以下方法展平 txt_records
:
locals {
txt_records_flat = merge([
for key, values in var.txt_records:
{for value in values:
"${key}-${value}" => {"record_name" = key, "record_value" = value}
}
]...)
}
这导致 local.txt_records_flat
个:
{
"@-bar" = {
"record_name" = "@"
"record_value" = "bar"
}
"@-foo" = {
"record_name" = "@"
"record_value" = "foo"
}
"mail._domainkey.self-foobar" = {
"record_name" = "mail._domainkey.self"
"record_value" = "foobar"
}
}
那你就用它:
resource "digitalocean_record" "this_txt_record" {
for_each = local.txt_records_flat
domain = var.domain
type = "TXT"
name = each.value.record_name
value = each.value.record_value
}