Terraform: 处理因功能标志而有条件的本地变量

huangapple go评论61阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2023年4月11日 06:41:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/75981253.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定