Terraform 允许在计划步骤中将值标记为“未知”,因为许多值可能只有在应用某些资源后才知道。
在计划步骤中,有什么方法可以检查某个值是已知还是未知?
具体来说,我希望能够做这样的事情:
locals {
foo = "hello world"
bar = uuid()
}
output "foo_known" {
value = knownatplan(local.foo)
}
output "bar_known" {
value = knownatplan(local.bar)
}
Outputs:
foo_known = true
bar_known = false
其中
knownatplan
是一个函数或某种其他机制,用于确定该值在计划时是否已知。
我现在有一点时间,可以实现 terraform 的发明者不太喜欢的一个很酷的“功能”。 ^-^
我是 terraform-provider-value (github) (terraformregistry) 的作者,在那里我有两个资源,称为
value_is_fully_known
(link) 和 value_is_known
(link)。听起来不错吗?只需查看文档或在此处查找一些示例。
从根本上讲,它们允许您在实际申请之前获得
true
或 false
的值,该值可能是“(应用后已知)”!这是一个例子:
terraform {
required_providers {
value = {
source = "pseudo-dynamic/value"
version = "0.5.1"
}
}
}
locals {
foo = "hello world"
bar = uuid()
}
resource "value_unknown_proposer" "default" {}
resource "value_is_known" "foo" {
value = local.foo
guid_seed = "foo"
proposed_unknown = value_unknown_proposer.default.value
}
resource "value_is_known" "bar" {
value = local.bar
guid_seed = "bar"
proposed_unknown = value_unknown_proposer.default.value
}
output "foo_known" {
value = value_is_known.foo.result
}
output "bar_known" {
value = value_is_known.bar.result
}
结果是:
Outputs:
bar_known = false
foo_known = true
具体来说,您正在经历两个计划阶段,一个我称之为计划阶段,另一个我称之为应用阶段,其中包括另一个独立且隐式的计划阶段。 当您看到计划、潜在结果(大多数时候有很多“应用后已知”值)和消息“Terraform 将执行以下操作:”时,计划阶段是典型的。 另一方面的应用阶段是,当您看到“应用完成!”时,所有值都在提供程序作者、terraform 或您的视图中计算。
实施可接受的解决方案非常具有挑战性,因为在计划阶段,提供商没有机会保存任何内容,因为没有任何东西是持久的。当应用阶段成功时,仅存储隐式计划阶段中的更改。
在应用之前了解某个值是已知还是未知,可以让您在 terraform 中实现一些很酷的工作流程,但它们应该非常有限。我只能鼓励您尽可能少地使用这种机制。但如果你有一些很酷的用法,你介意分享吗?请随意打开拉取请求添加示例或开始讨论。
我非常感谢任何形式的反馈(建设性的批评、错误和想法)。 =)
没有任何机制可以做到这一点,因为这样做会打破 Terraform 所依赖的完成其工作的假设:在最终应用步骤中用已知值替换未知值只能添加信息,而永远不能更改信息。上述保证是在产生任何副作用之前规划变革这一理念的基础。
其他语言不具备此机制的系统只能提供假设的“试运行”更改,这些更改可能不完整或不准确,而 Terraform 旨在做出额外的承诺,即它将按照计划中所示进行更改或返回一个错误,解释为什么不能。应用计划成功但生成的结果与计划报告的结果不同的任何情况都始终被视为错误,无论是在 Terraform Core 本身还是在相关提供程序中。未知的值是 Terraform 如何兑现这一承诺的重要组成部分。
我在博客文章未知值:Terraform Plan 的秘密中更详细地写了这一点。
使用 Teneko 的解决方案,我得到了以下用例:
假设有 2 个模块。
module "vnet" {
cidrs = ["1.1.1.1/24"]
}
module "vnet_peer" {
spoke_id = "subscription/xyz/etc"
hub_id = "subscription/abc/etc"
}
我们可以通过
subscription/xyz/etc
或 string
来声明 module output
。
如果并非所有配置都处于状态,我们需要通过
id
: 验证我们的 vnet 是否存在
#module.vnet_peer
data "azurerm_virtual_network" "spoke" {
name = split("/", var.spoke_id)[2]
resource_group_name = split("/", var.spoke_id)[1]
}
如果我们通过了
module output
,这有点毫无意义,但如果我们通过了string
,则需要。
另一种方法是获取所有 vnet,并检查我们的 vnet 是否在列表或密钥中:
data "azurerm_resources" "spokes" {
type = "Microsoft.Network/virtualNetworks"
}
data "azurerm_virtual_network" "spoke" {
for_each = {for r in data.azurerm_resources.spokes.resources: r.name => r}
name = each.key
resource_group_name = each.value.resource_group_name
}
locals {
in_list = contains(data.azurerm_resources.spokes.resources.*.id, var.spoke_id)
in_keys = contains(keys(data.azurerm_virtual_network.spoke), split("/", var.spoke_id)[2])
}
但是,除非我们发出明确的
depends_on
,否则该资源在应用时不会存在。这使得条件资源创建变得不可能。
但是:使用 Teneko 的提供商:
resource "azurerm_virtual_network_peering" "x" {
# if false, i.e. value known at apply, go ahead and plan the creation
# if true, i.e. string, validate that the vnet exists
count = value_is_known.foo.result ? local.in_list ? 1 : 0 : 1
...
}
最初并不知道价值。我们运行 apply,并重新评估配置,一旦它实际上是一个字符串,我们就验证 vnet 是否存在并获得相同的结果。
这使得可以使用
module output
或 string
方法进行部署,并验证部署。
但是,如果提供的输出不符合预期(可能是错误的输入),由于不一致,可能会导致应用失败