英文:
Terraform: Handling locals that are conditional due to feature flags
问题
I'm building a Terraform module that uses some variables for feature flags along with locals for storing some computed values. I'm bumping into some errors while a flag is true.
The flags (booleans saved as variables) are on every resource and use this convention which seems to be standard in Terraform:
resource "provider_resource_id" "resource_name" {
...
count = var.disable_resource ? 0 : 1
...
}
The provider outputs IDs when making these resources and because count
forces me to put an index on them, I'm saving them as locals in a locals.tf file to be less verbose:
locals {
resource_name_sid = provider_resource_id.resource_name[0].sid
}
I'm now running terraform apply
when disable_resource = true
and get this error: Invalid index: provider_resource_id.resource_name[0].sid (provider_resource_id.resource_name is empty tuple).
I see that defining the local when the resource isn't created is a problem. So I commented out the local. Now I get other errors on all resources expecting the local: Reference to undeclared local value: (resource_name_sid has not been declared).
These resources wouldn't actually be built due to the flag, but they still expect the local (which I can't define because the resource isn't being built).
I bet I can put a ternary on every local to say, for example:
locals {
resource_name_sid = var.disable_resource ? "" : provider_resource_id.resource_name[0].sid
}
But that is getting verbose again. Maybe I can't externalize these locals and use feature flags at the same time. (I did try moving locals into the resources file but got the same result.) Maybe I need to abandon the use of locals for storing these and just put them inline in the resources. Or is there something I am missing?
英文:
I'm building a Terraform module that uses some variables for feature flags along with locals for storing some computed values. I'm bumping into some errors while a flag is true.
The flags (booleans saved as variables) are on every resource and use this convention which seems to be standard in Terraform:
resource "provider_resource_id" "resource_name" {
...
count = var.disable_resource ? 0 : 1
...
}
The provider outputs IDs when making these resources and because count
forces me to put an index on them, I'm saving them as locals in a locals.tf file to be less verbose:
locals {
resource_name_sid = provider_resource_id.resource_name[0].sid
}
I'm now running terraform apply
when disable_resource = true
and get this error: Invalid index: provider_resource_id.resource_name[0].sid (provider_resource_id.resource_name is empty tuple)
. I see that defining the local when the resource isn't created is a problem. So I commented out the local. Now I get other errors on all resources expecting the local: Reference to undeclared local value: (resource_name_sid has not been declared)
These resources wouldn't actually be built due to the flag, but they still expect the local (which I can't define because the resource isn't being built).
I bet I can put a ternary on every local to say, for example:
locals {
resource_name_sid = var.disable_resource ? "" : provider_resource_id.resource_name[0].sid
}
But that is getting verbose again. Maybe I can't externalize these locals and use feature flags at the same time. (I did try moving locals into the resources file but got the same result.) Maybe I need to abandon the use of locals for storing these and just put them inline in the resources. Or is there something I am missing?
答案1
得分: 3
以下是您要翻译的部分:
没有办法避免向Terraform解释对象不存在的情况下应该发生什么,但有一些更短的方法来表达在资源实例数量为零时使用备用值作为占位符的想法。
一个简洁的选择是使用one
,它是一个用于处理将零个或一个元素的列表转换为可能为null
的常见情况的函数:
resource_name_sid = one(provider_resource_id.resource_name[*].sid)
}
provider_resource_id.resource_name[*].sid
生成与provider_resource_id.resource_name
的计数匹配长度的列表。在您的配置中,计数只能为零或一,这与one
的期望相匹配。
因此,local.resource_name_sid
将是一个sid
值或null
。
另一种可能性是使用try
来让元素查找[0]
失败,并提供备用值以供使用:
resource_name_sid = try(provider_resource_id.resource_name[0].sid, null)
}
这个选项让您选择一个不同的备用值来使用,尽管在Terraform中,null
通常表示值的缺失,所以我建议使用它,除非您有其他有效的SID值可以作为备用值使用。
使用null
的好处是您可以直接将local.resource_name_sid
分配给另一个资源的参数,然后在null
的情况下,对于提供程序来说,它将与完全省略该参数没有区别,因为null
还表示参数的缺失。
最后一个选项是直接测试provider_resource_id.resource_name
的长度,看看是否有零索引:
resource_name_sid = (
length(provider_resource_id.resource_name) > 0 ?
provider_resource_id.resource_name[0].sid :
null
}
这类似于您在问题中包含的条件语句,但它直接测试是否存在provider_resource_id.resource_name[0]
,而不是重复引用var.disable_resource
。
直接测试资源意味着,如果将来更改了count
定义,那么您不需要更新此表达式,只要您的新count
表达式仍然选择零或一个元素。
但是,这是最详尽的选项,并需要在两个地方重复使用长表达式provider_resource_id.resource_name
,因此,如果需要非空的备用值,通常会使用上面的try
选项,如果null
是足够的备用值,通常会使用one
选项。
与其他选项相比,one
函数还具有一个优势,即如果provider_resource_id.resource_name
存在多个实例,它将失败,因此如果将来更新此模块以拥有多个资源实例,则会通过错误提醒您更新其他表达式以处理两个或更多的SID值。其他表达式将默默地忽略其他SID。
英文:
There is no way to avoid explaining to Terraform what should happen in the case where the object doesn't exist, but there are some shorter ways to express the idea of using a fallback value as a placeholder when there are zero instances of the resource.
One concise option is to use one
, which is a function intended to deal with the common situation of turning a list of zero or one elements into a value that might be null
:
locals {
resource_name_sid = one(provider_resource_id.resource_name[*].sid)
}
provider_resource_id.resource_name[*].sid
produces a list of length matching the count of provider_resource_id.resource_name
. In your configuration the count can only be either zero or one, which matches the expectations of one
.
Therefore local.resource_name_sid
will either be a single sid
value or it will be null
.
Another possibility is to use try
to let the element lookup [0]
fail and provider a fallback value to use if it does:
locals {
resource_name_sid = try(provider_resource_id.resource_name[0].sid, null)
}
This option lets you choose a different fallback value to use instead of null
if you like, although null
is the typical way to represent the absense of a value in Terraform so I would suggest using that unless you have some other working SID value to use as a fallback.
Using null
has the advantage that you can then assign local.resource_name_sid
directly to an argument of another resource and then in the case where its null
it will be completely indistinguishable to the provider from having omitted that argument entirely, because null
also represents the absence of an argument.
A final option is to directly test the length of provider_resource_id.resource_name
to see if there is a zeroth index:
locals {
resource_name_sid = (
length(provider_resource_id.resource_name) > 0 ?
provider_resource_id.resource_name[0].sid :
null
}
This is similar to the conditional you included in your question but it directly tests whether there's a provider_resource_id.resource_name[0]
rather than repeating the reference to var.disable_resource
.
Testing the resource directly means that if you change the count
definition in future then you won't need to update this expression too, as long as your new count
expression still chooses between either zero or one elements.
However, this is the most verbose option and requires repeating the long expression provider_resource_id.resource_name
in two places, so I'd typically use the try
option above if I needed to have a non-null fallback value, and the one
option if null
is a sufficient fallback value.
The one
function also has the advantage over the others that it will fail if there is ever more than one instance of provider_resource_id.resource_name
, and so if you update this module to have multiple instances of that resource in future then you'll be reminded by the error to update your other expressions to deal with two or more SID values. The other expressions will just silently ignore the other SIDs.
答案2
得分: 1
很抱歉,目前还没有更好的方法来定义这个。你可以在大多数流行的模块中随处看到按索引引用值的参考(例如https://github.com/terraform-aws-modules/terraform-aws-vpc/blob/master/main.tf)。
即使是我,我也希望在我们希望根据条件创建资源(特性标志)时有一个更简单的处理方式。
英文:
Unfortunately there is no better way to define this as of now. You can see references to value by index everywhere in most of the popular modules (https://github.com/terraform-aws-modules/terraform-aws-vpc/blob/master/main.tf for example).
Even I've hoped that there was a simpler way to deal with this when we want resources to be created conditionally(feature flagged).
答案3
得分: 0
你对 count
的单次使用将会级联到其他所有地方。因此,你还需要在代码的其他地方检查 var.disable_resource == true
的条件。这包括 locals
部分,你可以按照以下方式编写:
locals {
resource_name_sid = var.disable_resource ? null : provider_resource_id.resource_name[0].sid
}
这将成功地跳过 resource_name_sid
的创建。但显然,现在你将不得不在其他任何使用 resource_name_sid
的地方继续使用这个条件。
英文:
Your single use of count
will cascade to everything else. So you will also have to check the var.disable_resource == true
condition in other places in your code. This includes locals
, which you can write as follows:
locals {
resource_name_sid = var.disable_resource ? null : provider_resource_id.resource_name[0].sid
}
This will successfully skip the resource_name_sid
creation. But obviously, now you will have to keep using the condition in ever other place where resource_name_sid
would be used.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论