Terraform中的条件属性

54
Terraform是否支持条件属性?我只想在变量的值决定时使用属性。
例子:
resource "aws_ebs_volume" "my_volume" {
  availability_zone = "xyz"
  size              = 30

  if ${var.staging_mode} == true:
    snapshot_id = "a_specific_snapshot_id"
  endif
}

上述包含属性snapshot_idif语句是我正在寻找的。Terraform是否支持基于变量值的属性包含?

6个回答

60

Terraform 0.12(尚未发布)还将带来对HCL2的支持,这使您可以使用类似以下内容的可空参数

resource "aws_ebs_volume" "my_volume" {
  availability_zone = "xyz"
  size              = 30
  snapshot_id       = var.staging_mode ? local.a_specific_snapshot_id : null
}

可空参数在这个0.12预览指南中有所涵盖。

对于0.12版本之前的Terraform,Markus答案可能是您最好的选择,尽管我会使用类似以下方式更明确地指定count

resource "aws_ebs_volume" "staging_volume" {
   count             = "${var.staging_mode ? 1 : 0}"
   availability_zone = "xyz"
   size              = 30

   snapshot_id = "a_specific_snapshot_id"
}

resource "aws_ebs_volume" "non_staging_volume" {
   count             = "${var.staging_mode ? 0 : 1}"
   availability_zone = "xyz"
   size              = 30
}

请注意,资源名称必须唯一,否则Terraform会发出投诉。如果您需要引用EBS卷,例如使用aws_volume_attachment,则会出现问题,因为在0.12之前三元表达式不是惰性的,所以像这样的内容是无法工作的:
resource "aws_volume_attachment" "ebs_att" {
  device_name = "/dev/sdh"
  volume_id   = "${var.staging_mode ? aws_ebs_volume.staging_volume.id : aws_ebs_volume.non_staging_volume.id}"
  instance_id = "${aws_instance.web.id}"
}

因为它将尝试评估三元运算符的两个方面,而任何时候只有一个可以是有效的。在Terraform 0.12中,这将不再是问题,但显然您可以更轻松地使用可空参数来解决它。


1
你可以通过使用join来避免这种懒惰,另请参阅我的更新答案 - Markus

11

我不知道有这样的功能,但是如果你的情况不太复杂,你可以围绕这个模型。由于布尔值 truefalse 被认为是 10,所以你可以在计数中使用它们。因此,你可以使用:

provider "null" {}

resource "null_resource" "test1" {
   count= ${var.condition ? 1 : 0}
}
resource "null_resource" "test2" {
   count = ${var.condition ? 0 : 1}
}

output "out" {
    value = "${var.condition ? join(",",null_resource.test1.*.id) : join(",",null_resource.test2.*.id) }"
}

由于 count 属性只创建其中一个资源。

您必须使用 join 来处理这些值,因为它似乎能优雅地处理其中一个值不存在的情况。

感谢 ydaetskcor他们的答案 中指出了变量处理的改进。


我认为这不会起作用,因为您有两个名称相同的资源“my_volume”。 - Basil Musa
@BasilMusa 我已经相应地更正了代码,加入了一个解决方法来仍然访问资源中的值。 - Markus
是的,但现在出现了一个新问题,切换将销毁test1并创建test2。条件属性应该能够处理更新资源。无论如何,好提示,在某些情况下非常有用。我会点赞。 - Basil Musa
没有测试过,我会认为如果您只是插入“snapshot_id”并再次进行计划,这也将重新创建资源。但是您是正确的,即使对于就地可更新属性,此方法也具有这种缺点。您可能需要等待HCL的新版本(正如ydaetskcoR所提到的)。 - Markus

10

现在,随着Terraform v0.12和相应的HCL2发布,您只需将默认变量值设置为“null”即可实现此目标。请查看来自Terraform网站的以下示例:

variable "override_private_ip" {
  type    = string
  default = null
}

resource "aws_instance" "example" {
  # ... (other aws_instance arguments) ...

  private_ip = var.override_private_ip
}

更多信息请参见:

https://www.hashicorp.com/blog/terraform-0-12-conditional-operator-improvements

这篇文章介绍了 Terraform 0.12 中的条件运算符改进。

1

在 Terraform 0.15 中有一个新的实验性功能:defaults,它与 optional 一起使用。

defaults 函数是一种专门用于处理输入变量的函数,其类型约束为对象类型或包含可选属性的对象类型集合。

来自文档:

terraform {
  # Optional attributes and the defaults function are
  # both experimental, so we must opt in to the experiment.
  experiments = [module_variable_optional_attrs]
}

variable "storage" {
  type = object({
    name    = string
    enabled = optional(bool)
    website = object({
      index_document = optional(string)
      error_document = optional(string)
    })
    documents = map(
      object({
        source_file  = string
        content_type = optional(string)
      })
    )
  })
}

locals {
  storage = defaults(var.storage, {
    # If "enabled" isn't set then it will default
    # to true.
    enabled = true

    # The "website" attribute is required, but
    # it's here to provide defaults for the
    # optional attributes inside.
    website = {
      index_document = "index.html"
      error_document = "error.html"
    }

    # The "documents" attribute has a map type,
    # so the default value represents defaults
    # to be applied to all of the elements in
    # the map, not for the map itself. Therefore
    # it's a single object matching the map
    # element type, not a map itself.
    documents = {
      # If _any_ of the map elements omit
      # content_type then this default will be
      # used instead.
      content_type = "application/octet-stream"
    }
  })
}

0

仅供帮助,这是一个更复杂的示例:

data "aws_subnet" "private_subnet" {
  count             = var.sk_count
  vpc_id            = data.aws_vpc.vpc.id
  availability_zone = element(sort(data.aws_availability_zones.available.names), count.index)

  tags = {
    Name = var.old_cluster_fqdn != "" ? "${var.old_cluster_fqdn}-prv-subnet-${count.index}" : "${var.cluster_fqdn}-prv-subnet-${count.index}"
  }
}

0

自从我在Google上找到这个答案并对其感到困惑,我觉得我最好更新一下答案。

在locals块中使用的defaults()函数已经不存在了。

对于我的用例,我改为使用optional的第二个参数。

variable "settings" {
  type = object({
    systemd_base = optional(string, "/etc/systemd/system")
  })
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接