158

I have resources defined in .tf files that are generic to several applications. I populate many of the fields via a .tfvars file. I need to omit some of the resources entirely based on variables in the .tfvars.

For example if I have a resource like:

resource "cloudflare_record" "record" {
  zone_id = "${data.cloudflare_zones.domain.zones[0].id}"
  name    = "${var.subdomain}"
  value   = "${var.origin_server}"
  type    = "CNAME"
  ttl     = 1
  proxied = true
}

But then I declare something like cloudflare = false in my .tfvars file I'd like to be able to do something like this:

if var.cloudflare {
  resource "cloudflare_record" "record" {
    zone_id = "${data.cloudflare_zones.domain.zones[0].id}"
    name    = "${var.subdomain}"
    value   = "${var.origin_server}"
    type    = "CNAME"
    ttl     = 1
    proxied = true
 }
}

I've looked at dynamic blocks but that looks like you can only use those to edit fields and blocks within a resource. I need to be able to ignore an entire resource.

Joel Guerra
  • 4,760
  • 4
  • 12
  • 26

4 Answers4

232

Add a count parameter with a ternary conditional using the variable declared in .tfvars like this:

resource "cloudflare_record" "record" {
  count = var.cloudflare ? 1 : 0
  zone_id = "${data.cloudflare_zones.domain.zones[0].id}"
  name    = "${var.subdomain}"
  value   = "${var.origin_server}"
  type    = "CNAME"
  ttl     = 1
  proxied = true
}

In this example var.cloudflare is a boolean declared in the .tfvars file. If it is true a count of 1 record will be created. If it is false a count of 0 record will be created.

After the count apply the resource becomes a group, so later in the reference use 0-index of the group:

cloudflare_record.record[0].some_field
Joel Guerra
  • 4,760
  • 4
  • 12
  • 26
  • 5
    Is there a way as well that will allow referencing this resource without using an index? e.g. `cloudflare_record.record[0].name`? – Ilya Chernomordik Feb 18 '21 at 17:22
  • 9
    Index will be required if you use the count feature. If you don't want to hard code the 0 you can use count.index. See: https://www.terraform.io/docs/language/meta-arguments/count.html – Joel Guerra Feb 24 '21 at 02:04
  • 4
    what if resource doesn't get create due to count field, then following line will fail. ```cloudflare_record.record[0].some_field```.how to output this value then – Vishal Sharma May 07 '21 at 12:40
  • 1
    @VishalSharma - New to this myself, but someone suggested here that you could maybe use an asterisk `*` in place of the brackets (at least when interpolating in a string): https://stackoverflow.com/questions/51654863/terraform-variables-and-count-0 – patricknelson Oct 05 '21 at 08:46
  • @JoelGuerra how should you deal with the output, lets say I want to use some output of cloudflare_record, but since the block has not even executed because of count, how my output will understand? – Ashish Kumar Sep 09 '22 at 18:05
  • maybe use the [try function](https://www.terraform.io/language/functions/try) to populate output variables – Jason Oct 06 '22 at 16:18
  • be nice if there was something a bit cleaner that didn't require us to suddenly use an index like `condition = var.cloudflare ? 1 : 0` (*for people skim-reading do not use this!*) – Josh Mc Mar 28 '23 at 00:18
  • in the case of count 0, are the other argument/parameter values required or can you default to null? – mike01010 Sep 01 '23 at 20:05
31

Expanding on @Joel Guerra's answer, after you use count to determine whether to deploy the resource or not, you can use the one() function to refer to the resource without an index (i.e. without having to use [0]).

For example, after defining the resource like below

resource "cloudflare_record" "record" {
  count = var.cloudflare ? 1 : 0
}

Define a local variable like below

locals {
  cloudflare_record_somefield = one(cloudflare_record.record[*].some_field)
}

Now instead of cloudflare_record.record[0].some_field, you can use

local.cloudflare_record_somefield

If the count is 0 (e.g. var.cloudflare is false and the resource wasn't created) then local.cloudflare_record_somefield would return null (instead of returning an error when indexing using [0]).

Reference: https://developer.hashicorp.com/terraform/language/functions/one

Trapsilo Bumi
  • 910
  • 8
  • 11
4

An issue i'm seeing this with is if the resource your trying to create is already using a for_each then you can't use both count and for_each in the resource. I'm still trying to find an answer on this will update if I find something better.

1

Sample Scenario: You might want to create or not create (toggle / use flag / conditionally create) a VM. But along with the VM, you might also have to create/not create its Load Balancer, Target group, and Security Group etc.

The problem with other answers on the internet is that when you use a ternary operator on a resource and when you try to reference it on some other resource you will always get a reference error or index error or Empty Tuple Error.

To solve this issue while pointing to the conditional resource you can use the try syntax

resource "aws_vpc" "main" {
  count = var.test_flag ? 1 : 0
  cidr_block       = var.vpc_cidr
  instance_tenancy = var.vpc_instance_tenancy

  tags = {
     Name = "${var.cluster_name}-vpc"
  }

}

resource "aws_internet_gateway" "gw" {
  count =   try(var.test_flag ? 1 : 0, 0) // try block is important here because it has a dependency, here the VPC, but VPC might not need a try block because it is the parent.

  vpc_id = aws_vpc.main[0].id

  tags = {
    Name = "${var.cluster_name}-IG"
  }
}
Shivam Anand
  • 952
  • 1
  • 10
  • 21