51

Does Terraform support conditional attributes? I only want to use an attribute depending on a variable's value.

Example:

resource "aws_ebs_volume" "my_volume" {
  availability_zone = "xyz"
  size              = 30

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

The above if statement enclosing the attribute snapshot_id is what I'm looking for. Does Terraform support such attribute inclusion based on a variable's value.

ydaetskcoR
  • 53,225
  • 8
  • 158
  • 177
Basil Musa
  • 8,198
  • 6
  • 64
  • 63

5 Answers5

59

Terraform 0.12 (yet to be released) will also bring support for HCL2 which allows you to use nullable arguments with something like this:

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

Nullable arguments are covered in this 0.12 preview guide.

For version of Terraform before 0.12, Markus's answer is probably your best bet although I'd be more explicit with the count with something like this:

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
}

Note that the resource names must be unique or Terraform will complain. This then causes issues if you need to refer to the EBS volume such as with an aws_volume_attachment as in pre 0.12 the ternary expression is not lazy so something like this doesn't work:

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

Because it will attempt to evaluate both sides of the ternary where only one can be valid at any point. In Terraform 0.12 this will no longer be the case but obviously you could solve it more easily with the nullable arguments.

ydaetskcoR
  • 53,225
  • 8
  • 158
  • 177
  • 1
    You can get around the laziness by using `join`, see also my [updated answer](https://stackoverflow.com/a/51497455/4210388). – Markus Jul 25 '18 at 11:02
11

I'm not aware of such a feature, however, you can model around this, if your cases are not too complicated. Since the Boolean values true and false are considered to be 1 and 0, you can use them within a count. So you may use

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

Only one of the two resources is created due to the count attribute.

You have to use join for the values, because this seems to handle the inexistence of one of the two values gracefully.

Thanks ydaetskcor for pointing out in their answer the improvements for variable handling.

Markus
  • 684
  • 5
  • 11
  • I dont think this will work since you are having two resources with the same name "my_volume" – Basil Musa Jul 24 '18 at 14:05
  • @BasilMusa I corrected the code accordingly, adding a workaround to still access values from the resources. – Markus Jul 25 '18 at 11:01
  • Yes, but now a new problem comes up, toggling will destroy test1 and create test2. A conditional attribute should be able to handle updating a resource. Anyway, good tip and useful in certain cases. I'll upvote. – Basil Musa Jul 25 '18 at 14:22
  • Without testing it, I would assume that if you just insert the `snapshot_id` and do a plan again, this would also recreate the resource. But you are right, this method has this drawback, even for in-place updatable attributes. You may have to wait for the new version of HCL (as ydaetskcoR mentioned). – Markus Jul 26 '18 at 08:49
10

Now that Terraform v0.12 and respective HCL2 were released, you can achieve this by just setting the default variable value to "null". Look at this example from Terraform website:

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

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

  private_ip = var.override_private_ip
}

More info here:

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

1

There is a new experimental feature with Terraform 0.15 : defaults which works with optional.

The defaults function is a specialized function intended for use with input variables whose type constraints are object types or collections of object types that include optional attributes.

From documentation :

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"
    }
  })
}
Antoine
  • 4,456
  • 4
  • 44
  • 51
0

just for the help, a more complex example:

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