【问题标题】:Conditional attributes in TerraformTerraform 中的条件属性
【发布时间】:2018-12-31 22:44:37
【问题描述】:

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_id 属性的if 语句是我正在寻找的。 Terraform 是否支持基于变量值的此类属性包含。

【问题讨论】:

    标签: terraform terraform-provider-aws


    【解决方案1】:

    Terraform 0.12(尚未发布)还将带来support for HCL2,它允许您使用nullable arguments,如下所示:

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

    this 0.12 preview guide 涵盖了可为空的参数。

    对于 0.12 之前的 Terraform 版本,Markus 的 answer 可能是你最好的选择,尽管我会更明确地使用 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 (如 pre 0.12 the ternary expression is not lazy),那么这会导致问题,所以这样的事情不起作用:

    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 中,情况将不再如此,但显然您可以使用可为空的参数更轻松地解决它。

    【讨论】:

    • 你可以使用join来解决懒惰,也可以看看我的updated answer。
    【解决方案2】:

    我不知道有这样的功能,但是,如果您的案例不太复杂,您可以围绕它进行建模。由于布尔值true 和false 被认为是1 和0,因此您可以在计数中使用它们。所以你可以使用

    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 在their answer 中指出对变量处理的改进。

    【讨论】:

    • 我认为这行不通,因为您有两个同名的资源“my_volume”
    • @BasilMusa 我相应地更正了代码,添加了一种解决方法以仍然从资源中访问值。
    • 是的,但是现在出现了一个新问题,切换会破坏 test1 并创建 test2。条件属性应该能够处理更新资源。无论如何,好的提示和在某些情况下有用。我会投票。
    • 如果不对其进行测试,我会假设如果您只是插入 snapshot_id 并再次执行计划,这也会重新创建资源。但你是对的,这种方法有这个缺点,即使对于就地可更新属性也是如此。您可能需要等待 HCL 的新版本(如 ydaetskcoR 所述)。
    【解决方案3】:

    现在 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

    【讨论】:

      【解决方案4】:

      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"
          }
        })
      }
      

      【讨论】:

        【解决方案5】:

        只是为了帮助,一个更复杂的例子:

        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}"
          }
        }
        

        【讨论】:

          猜你喜欢
          • 2019-09-22
          • 2022-01-21
          • 2021-12-24
          • 1970-01-01
          • 1970-01-01
          • 2019-08-16
          • 2019-12-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多